class Worker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)

    def run(self):
        # """Long-running task."""
        # for i in range(5):
        #     sleep(1)
        #     self.progress.emit(i + 1)
        # self.finished.emit()
        isRun = True  # idk maybe I will do something with this later

        while isRun:
            time.sleep(30)
            create_timelapse_from_viewport()
Esempio n. 2
0
class UbiquitiSignalScraper(QtCore.QObject):

    # Received Signals
    start = pyqtSignal()
    setInterrupt = pyqtSignal()

    def __init__(self, MainWindow, IP, username, password):
        super(UbiquitiSignalScraper, self).__init__()
        self.mainWindow = MainWindow
        self.signalScraperInterrupt = False

        # Characteristics
        self.updateSpeed = 0.5  # in seconds

        # Emitted Signals
        self.mainWindow.ubiquitiNewSignalStrength.connect(
            self.mainWindow.updateUbiquitiSignalStrength)
        self.mainWindow.ubiquitiScraperError.connect(
            self.mainWindow.handleUbiquitiScraperError)

        self.ip = IP
        # Start the scraper
        scraper.begin('Drivers/chromedriver.exe',
                      self.ip,
                      username=username,
                      password=password)

    def run(self):
        """ Scrapes signal strength from the modem and provides info to main window """
        self.signalScraperInterrupt = False

        while not self.signalScraperInterrupt:
            time.sleep(self.updateSpeed)  # frequency
            try:
                strength = scraper.fetch_signal(self.ip)
                self.mainWindow.ubiquitiNewSignalStrength.emit(strength)
            except json.decoder.JSONDecodeError:
                self.mainWindow.ubiquitiScraperError.emit('JSON Fetch')

            QCoreApplication.processEvents(
            )  # Allow the thread to process events

    def interrupt(self):
        self.signalScraperInterrupt = True

    def setUpdateSpeed(self, speed):
        self.updateSpeed = speed
        print("Set update speed to: " + str(self.updateSpeed))
Esempio n. 3
0
class EntryWidget(QWidget):
    value_changed_signal = pyqtSignal(tuple)

    def __init__(self, name, value, layout=QVBoxLayout, entry=QDoubleSpinBox,
                 *args, **kwargs):
        # self.value_changed_signal = pyqtSignal(tuple)
        super(EntryWidget, self).__init__(*args, **kwargs)
        self.setObjectName(name)
        self.value = value
        layout = layout()
        self.entry = entry()
        self.entry_type = type(value)
        if entry == QDoubleSpinBox:
            self.entry.setMinimum(-10000.0)
            self.entry.setMaximum(10000.0)
        # self.signal = pyqtSignal(dict)
        label = QLabel(name)
        label.setAlignment(Qt.AlignHCenter)
        layout.addWidget(label)
        layout.addWidget(self.entry)
        self.entry.setValue(value)
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(0)
        self.setSizePolicy(MAXIMUM, MAXIMUM)

        self.entry.valueChanged.connect(self.on_entry_value_changed)

    @pyqtSlot()
    def on_entry_value_changed(self, value):
        self.value_changed_signal.emit((self.objectName(), value))
Esempio n. 4
0
class Model(QObject):
    """Responsible for holding and managing the data/state of the simulation."""
    value_changed = pyqtSignal(tuple)

    def __init__(self):
        super().__init__()
        # TODO: hard coding _base/_exp values here is a bad code smell.
        # There should be one code source for these values,
        # for initializing both the model and the view
        # (default values for QDoubleSpinBox entries).
        self._base = 1
        self._exp = 2
        self._x = np.linspace(0, 10, 800)
        self._update_y()

    def _update_y(self):
        self._y = (self._x * self._base) ** self._exp

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    def update(self):
        # self._base = base
        # self._exp = exp
        self._update_y()

    @property
    def base(self):
        return self._base

    @base.setter
    def base(self, value):
        self._base = value
        self.update()
        self.value_changed.emit((self._x, self._y))

    @property
    def exp(self):
        return self._exp

    @exp.setter
    def exp(self, value):
        self._exp = value
        self.update()
        self.value_changed.emit((self._x, self._y))
Esempio n. 5
0
class MyWindow2(QWidget):
    '''自定义窗口'''
    # 知识点:
    # 1.为了得到返回值用到了自定义的信号/槽
    # 2.为了显示动态数字,使用了计时器

    before_close_signal = pyqtSignal(int)  # 自定义信号(int类型)

    def __init__(self):
        super().__init__()

        self.sec = 0
        self.setWindowTitle('自定义窗口')
        self.resize(200, 150)

        self.lcd = QLCDNumber(18, self)
        btn1 = QPushButton(self, text="测试")
        btn2 = QPushButton(self, text="关闭")

        layout = QVBoxLayout(self)
        layout.addWidget(self.lcd)
        layout.addWidget(btn1)
        layout.addWidget(btn2)

        self.timer = QTimer()

        self.timer.timeout.connect(self.update)  # 每次计时结束,触发update
        btn1.clicked.connect(self.startTimer)
        btn2.clicked.connect(self.stopTimer)  # 去到关闭前的处理

    def update(self):
        self.sec += 1
        self.lcd.display(self.sec)  # LED显示数字+1

    def startTimer(self):
        self.timer.start(1000)  # 计时器每秒计数

    def stopTimer(self):
        self.timer.stop()
        self.sec = 0
        self.before_close_signal.emit(self.lcd.value())  # 发送信号,带参数 888
        self.close()  # 然后窗口关闭
Esempio n. 6
0
class QFrame(QObject):
    """
    This is a dummy class to workaround PyQt5 restrictions on declaring signals
    Basically, it does not allow to be declared in an instance of a class, or it will become bounded
    You need to define it as a class variable, but that does not work every time, especially for dynamic tasks
    In short -
    Okay:
        class A:
            smth = pyqtSignal()
    Not okay:
        class A:
            def __init__()
            self.smth = pyqtSignal()

    But both of the examples provide the same functionality, A.smth is a pyqtSignal
    So we workaround it, making a dummy class and going with the "okay" route
    This is essential for multicam support

    ! Probably have a tax on the performance
    """
    signal = pyqtSignal(QImage)
Esempio n. 7
0
class CameraLinkTest(QThread):
    test_signal = pyqtSignal(str)  # 括号里填写信号传递的参数

    def __init__(self, video_url, ip, port):
        QThread.__init__(self)
        self.video_url = video_url
        self.ip = ip
        self.port = port

    def run(self):
        print("in video_link_test")
        if not self.test_ip_port():
            msg = "连接不通"
        else:
            msg = "请检查账号和密码"
            cap = cv2.VideoCapture(self.video_url)
            if cap.isOpened():
                msg = "连接成功"
            cap.release()
        self.test_signal.emit(msg)

    def test_ip_port(self):
        ip_port_ok = False
        try:
            if self.video_url == "" or self.ip == "" or self.port == "":
                return
            sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sk.settimeout(1)
            try:
                sk.connect((self.ip, int(self.port)))
                print('Server port OK!')
                print((self.ip, int(self.port)))
                ip_port_ok = True
            except Exception as e:
                print("!!! error test_ip_port, e = ", str(e))
                print('Server port not connect!')
            sk.close()
        finally:
            return ip_port_ok
Esempio n. 8
0
class GetIridium(QtCore.QObject):

    # Received Signals
    start = pyqtSignal()
    setInterrupt = pyqtSignal()

    def __init__(self, MainWindow, host, user, password, name, IMEI):
        super(GetIridium, self).__init__()
        self.mainWindow = MainWindow
        self.dbHost = host
        self.dbUser = user
        self.dbPass = password
        self.dbName = name
        self.IMEI = IMEI
        self.iridiumInterrupt = False

        # Emitted Signals
        self.mainWindow.noIridium.connect(self.mainWindow.iridiumNoConnection)
        self.mainWindow.iridiumNewLocation.connect(
            self.mainWindow.updateBalloonLocation)

    def getApiData(self, imei):
        url = "http://eclipse.rci.montana.edu/php/antennaTracker.php?imei=%s" % imei
        try:
            # Timeout may be redundant, if port 80 is timing out, port 3306
            # will probably also
            response = urlopen(url, timeout=5)
            data = response.read().decode("utf-8")
            return json.loads(data)
        except:
            return {}

    def run(self):
        """ Gets tracking information from the Iridium satellite modem by taking the information from the web api OR the SQL database at Montana State University """
        # modified this to use the Web API - [email protected]
        #     the modification is crude and should be refactored. :P

        self.iridiumInterrupt = False
        prev = ''
        connectAttempts = 0
        while (not self.iridiumInterrupt):
            time.sleep(2)
            # Fetch the data from the API
            get_data = self.getApiData(self.IMEI)
            if get_data:
                # set the data from the API values
                dataMethod = "API"
                remoteTime = get_data['remoteTime']
                remoteHours = int(get_data['remoteHours'])
                remoteMinutes = int(get_data['remoteMinutes'])
                remoteSeconds = int(get_data['remoteSeconds']) + (
                    60 * remoteMinutes) + (3600 * remoteHours)
                remoteLat = float(get_data['remoteLat'])
                remoteLon = float(get_data['remoteLon'])
                remoteAlt = float(get_data['remoteAlt'])
                ### Create a new location object ###
                try:
                    newLocation = BalloonUpdate(remoteTime, remoteSeconds,
                                                remoteLat, remoteLon,
                                                remoteAlt, "Iridium",
                                                self.mainWindow.groundLat,
                                                self.mainWindow.groundLon,
                                                self.mainWindow.groundAlt)
                except:
                    print(
                        "Error creating a new balloon location object from Iridium Data"
                    )

                try:
                    # Notify the main GUI of the new location
                    self.mainWindow.iridiumNewLocation.emit(newLocation)
                except (Exception, e):
                    print(str(e))

            else:
                # use the database
                # Connect to the SQL Database (try 20 times)
                connected = False
                while (not connected and not self.iridiumInterrupt):
                    QtGui.QApplication.processEvents()
                    if connectAttempts < 20:
                        try:
                            # Connect to the database
                            db_local = MySQLdb.connect(host=self.dbHost,
                                                       user=self.dbUser,
                                                       passwd=self.dbPass,
                                                       db=self.dbName)
                            # prepare a cursor object using cursor() method
                            cursor = db_local.cursor()
                            sql = "select gps_fltDate,gps_time,gps_lat,gps_long,gps_alt from gps where gps_IMEI = %s order by pri_key DESC LIMIT 1" % (
                                IMEI)
                            cursor.execute(sql)
                            connected = True
                            if self.iridiumInterrupt:
                                cursor.close()
                                db_local.close()
                                connected = True
                        except:
                            print(
                                "Failed to connect to database, trying again in 1 sec"
                            )
                            connectAttempts += 1
                    else:
                        print("Failed to connect to database too many times")
                        self.interrupt()
                        self.mainWindow.noIridium.emit()
                if connected:
                    ### Fetch a single row using fetchone() method. ###
                    # POL: Note, there will only ever be one row, since we are
                    # using "LIMIT 1"
                    try:
                        results = cursor.fetchone()
                        if (results != prev):
                            prev = results
                            remoteTime = results[1].split(":")
                            remoteHours = int(remoteTime[0])
                            remoteMinutes = int(remoteTime[1])
                            remoteSeconds = int(remoteTime[2])
                            remoteTime = results[1]
                            remoteSeconds = remoteSeconds + \
                                (60 * remoteMinutes) + (3600 * remoteHours)
                            # http://stackoverflow.com/questions/379906/parse-string-to-float-or-int
                            remoteLat = float(results[2])
                            remoteLon = float(results[3])
                            # (meters to feet conversion)
                            remoteAlt = float(results[4]) * 3.280839895

                            ### Create a new location object ###
                            try:
                                newLocation = BalloonUpdate(
                                    remoteTime, remoteSeconds, remoteLat,
                                    remoteLon, remoteAlt, "Iridium",
                                    self.mainWindow.groundLat,
                                    self.mainWindow.groundLon,
                                    self.mainWindow.groundAlt)
                            except:
                                print(
                                    "Error creating a new balloon location object from Iridium Data"
                                )

                            try:
                                # Notify the main GUI of the new location
                                self.mainWindow.iridiumNewLocation.emit(
                                    newLocation)
                            except (Exception, e):
                                print(str(e))
                    except:
                        print(
                            "ERROR PARSING DATA FROM DATABASE: Cannot parse data or data may not exist, please double check your IMEI number"
                        )
                else:
                    print("ERROR: Unable to connect to database!")

        ### Clean up ###
        try:
            cursor.close()
            db_local.close()
        except:
            print("Error closing database")

        self.iridiumInterrupt = False

    def interrupt(self):
        self.iridiumInterrupt = True
Esempio n. 9
0
class GetAPRS(QtCore.QObject):

    # Received Signals
    start = pyqtSignal()
    setInterrupt = pyqtSignal()

    def __init__(self, MainWindow, APRS):
        super(GetAPRS, self).__init__()
        self.mainWindow = MainWindow
        self.aprsSer = APRS
        self.aprsInterrupt = False

        # Emitted Signals
        self.mainWindow.aprsNewLocation.connect(
            self.mainWindow.updateBalloonLocation)

    def run(self):
        """ Gets tracking information from the APRS receiver """

        aprsSer = self.APRS.getDevice()

        while (not self.aprsInterrupt):
            ### Read the APRS serial port, and parse the string appropriately                               ###
            # Format:
            # "Callsign">CQ,WIDE1-1,WIDE2-2:!"Lat"N/"Lon"EO000/000/A="Alt"RadBug,23C,982mb,001
            # ###
            try:
                line = str(aprsSer.readline())
                print(line)
                idx = line.find(self.callsign)
                if (idx != -1):
                    line = line[idx:]
                    line = line[line.find("!") + 1:line.find("RadBug")]
                    line = line.split("/")

                    # Get the individual values from the newly created list ###
                    time = datetime.utcfromtimestamp(
                        time.time()).strftime('%H:%M:%S')
                    lat = line[0][0:-1]
                    latDeg = float(lat[0:2])
                    latMin = float(lat[2:])
                    lon = line[1][0:line[1].find("W")]
                    lonDeg = float(lon[0:3])
                    lonMin = float(lon[3:])
                    lat = latDeg + (latMin / 60)
                    lon = -lonDeg - (lonMin / 60)
                    alt = float(line[3][2:])
                    aprsSeconds = float(time.split(':')[0]) * 3600 + float(
                        time.split(':')[1]) * 60 + float(time.split(':')[2])

                    ### Create a new location object ###
                    try:
                        newLocation = BalloonUpdate(time, aprsSeconds, lat,
                                                    lon, alt, "APRS",
                                                    self.mainWindow.groundLat,
                                                    self.mainWindow.groundLon,
                                                    self.mainWindow.groundAlt)
                    except:
                        print(
                            "Error creating a new balloon location object from APRS Data"
                        )

                    try:
                        # Notify the main GUI of the new location
                        self.aprsNewLocation.emit(newLocation)
                    except (Exception, e):
                        print(str(e))
            except:
                print("Error retrieving APRS Data")

        ### Clean Up ###
        try:
            aprsSer.close()  # Close the APRS Serial Port
        except:
            print("Error closing APRS serial port")

        self.aprsInterrupt = False

    def interrupt(self):
        self.aprsInterrupt = True
Esempio n. 10
0
class Client_API(QObject):
    loggedIn = pyqtSignal(dict)
    login_failed = pyqtSignal(str, str)
    registered = pyqtSignal()
    registration_failed = pyqtSignal(str, str)
    timeout_error = pyqtSignal()

    request_timeout = 10
    read_timeout = 60

    def __init__(self,
                 cfg,
                 tracker=None,
                 server_addr=None,
                 events_server_addr=None,
                 sharing_server_addr=None,
                 upload_server_addr=None,
                 parent=None):

        logger.debug("Initializing API server client...")
        QObject.__init__(self, parent=parent)

        self._sessions = dict()

        self.server_addr = server_addr if server_addr \
            else API_URI.format(cfg.host)
        self.events_server_addr = events_server_addr if events_server_addr \
            else API_EVENTS_URI.format(cfg.host)
        self.sharing_server_addr = sharing_server_addr if sharing_server_addr \
            else API_SHARING_URI.format(cfg.host)
        self.upload_server_addr = upload_server_addr if upload_server_addr \
            else API_UPLOAD_URI.format(cfg.host)
        self._tracker = tracker
        self.ip_addr = None
        self.cfg = cfg
        self.node_sign = None
        self._ip_lock = QMutex(parent=self)
        self._os_name, self._is_server = get_os_name_and_is_server()

    def emit_loggedIn(self, user_email, password_hash, user_id, node_id,
                      servers, license_type, remote_actions, last_event_uuid):
        '''
        Prepares data and emits loggedIn signal

        @param server Info on servers as returned by API server [dict]
        '''

        data = {
            'user_email': user_email,
            'password_hash': password_hash,
            'user_hash': self.cfg.user_hash,
            'node_hash': self.cfg.node_hash,
            'user_id': user_id,
            'node_id': node_id,
            'servers': servers,
            'license_type': license_type,
            'remote_actions': remote_actions,
            'last_event_uuid': last_event_uuid,
        }

        # Emit signal
        self.loggedIn.emit(data)

    def signup(self, fullname, email, password):
        """
        Registers new user on the API server

        @param fullname Users full name [unicode]
        @param email Users email address [str]
        @param password Users password [str]
        """
        signed, res = self._signup(fullname, email, password)

        if not signed:
            if res and 'errcode' in res and \
                    res['errcode'] == 'ERROR_NODEHASH_EXIST':
                self.cfg.set_settings(
                    {'node_hash': sha512(str(uuid4())).hexdigest()})
                return self.signup(fullname, email, password)

        if signed:
            self.registered.emit()

            if self._tracker:
                self._tracker.session_signup()
        else:
            error = 'Network Error' if res is None else res['errcode']
            self.registration_failed.emit(
                error,
                res.get('info', 'Unknown error') if res else 'Unknown error')

            if self._tracker:
                self._tracker.session_signup_failed(error)

        return signed, res

    def _signup(self, fullname, email, password):
        if self._tracker:
            self._tracker.session_signup_start()

        data = {
            'node_devicetype': 'desktop',
            'node_ostype': get_platform(),
            'node_osname': self._os_name,
            'node_name': get_device_name(),
            'fullname': fullname,
            'user_email': email,
            'user_password': password,
        }
        signed = False

        _res = self.create_request(action='signup', data=data)

        if _res and "success" in _res['result'] and 'user_hash' in _res:
            self.cfg.set_settings({'user_hash': _res['user_hash']})
            signed = True
            logger.info("Registered successfully")

        return signed, _res

    def login(self, login=None, password=None, user_hash=None):
        """
        Authorizes user on the API server

        @param login Users email address [str]
        @param password Users password [str]
        """
        logged_in, res = self._login(login, password, user_hash)

        if logged_in:
            license_type = res.get('license_type', None)
            license_type = license_type_constant_from_string(license_type)
            remote_actions = res.get('remote_actions', None)
            servers = list(map(self._strip_server_info, res.get('servers',
                                                                [])))
            try:
                logger.verbose("Servers: '%s'", servers)
            except AttributeError:
                pass
            res['servers'] = servers
            # Signalize on successful logging in
            self.emit_loggedIn(
                user_email=login if login else self.cfg.user_email,
                password_hash=password
                if password else self.cfg.user_password_hash,
                user_id=res.get('user_id', None),
                node_id=res.get('node_id', None),
                servers=servers,
                license_type=license_type,
                remote_actions=remote_actions,
                last_event_uuid=res.get('last_event_uuid', None))

            if self._tracker:
                self._tracker.session_login(license_type)
        else:
            error = 'Network Error' if res is None else res['errcode']
            self.login_failed.emit(
                error,
                res.get('info', 'Unknown error') if res else 'Unknown error')

            if self._tracker:
                self._tracker.session_login_failed(error)

        return logged_in, res

    def _login(self, login, password, user_hash):
        data = {
            'node_devicetype': 'desktop',
            'node_ostype': get_platform(),
            'node_osname': self._os_name,
            'node_name': get_device_name(),
            'user_email': login,
            'user_password': password,
            'user_hash': user_hash,
            'is_server': self._is_server,
        }

        # Signalize login attempt started (for statistics)
        if self._tracker:
            self._tracker.session_login_start()

        logged_in = False
        _res = self.create_request(action='login', data=data)
        if _res is None:
            if self._tracker:
                self._tracker.session_login_failed("response is None")
            return logged_in, _res
        if 'user_hash' not in _res:
            if self._tracker:
                self._tracker.session_login_failed("missing user_hash")
            logger.error("Server not returned user_hash")
            return logged_in, _res
        # Registered successfully
        if "success" in _res['result']:
            logged_in = True
            self.cfg.set_settings({'user_hash': _res['user_hash']})
            logger.info("Logged in successfully")

        return logged_in, _res

    def _strip_server_info(self, server_info):
        return dict(map(self._strip_info, server_info.items()))

    def _strip_info(self, item):
        key, value = item
        if isinstance(value, str):
            value = value.strip()
        return key, value

    def logout(self):
        self.create_request(action='logout')

    def generate_node_sign(self):
        """
        Returns node_sign parameter to be passed to API server

        @return Node sign value [str]
        """

        s = str(self.cfg.node_hash + str(self.ip_addr))
        s = s.encode()
        s = sha512(s)
        return s.hexdigest()

    def update_node_sign(self):
        self.update_ip()
        self.node_sign = self.generate_node_sign()

    def change_password(self, old_password, new_password):
        data = {'old_password': old_password, 'new_password': new_password}
        return self.create_request(action='changepassword', data=data)

    def get_ip(self):
        data = {'get': 'candidate'}
        encoded = self.make_json_data(action='stun', data=data)
        response = self.make_post(url=self.server_addr, data=encoded)
        try:
            response = json.loads(response)
            if response['result'] != 'success':
                return None
        except:
            return None
        try:
            return int(response['info'])
        except Exception as e:
            logger.error("Invalid response '%s' (%s)", e, response)
            return None

    def update_ip(self):
        self._ip_lock.lock()
        try:
            self.ip_addr = self.get_ip()
        finally:
            self._ip_lock.unlock()

    @staticmethod
    def make_json_data(action, data=()):
        '''
        Retuns complete request data encoded in JSON format
        @param action Name of request action [str]
        @param data Request data [dict]
        @return JSON encoded request data [string]
        '''

        data = {} if not data else data
        try:
            encoded = json.dumps({'action': action, 'data': data})
        except Exception as e:
            logger.error(
                "Failed to encoded request '%s' data %s into JSON format (%s)",
                action, repr(data), e)
            raise
        return encoded

    def get_request_data(self, action, data=(), force_update=False):
        """
        Add auth data to request data given and return its JSON encoded version

        @param action Request data 'action' field [string]
        @param data  Request data 'data' field [dict]
        @return JSON-encoded request data [string]
        """
        data = {} if not data else data
        if not self.node_sign or force_update:
            self.update_node_sign()

        # Add auth data
        if action not in ('signup', 'login'):
            data['user_hash'] = self.cfg.user_hash

        data['node_hash'] = self.cfg.node_hash
        data['node_sign'] = self.node_sign

        return self.make_json_data(action=action, data=data)

    def get_upload_request_data(self, fields, callback, force_update=False):
        """
        Add auth data to upload request fields given and return
        multipart/form data object

        @param fields Request data 'fields' [dict]
        @param callback Callback to be called on file chunk read [Function]
        @param force_update Instructs to update node sign [bool]
        @return multipart/form data object [MultipartEncoderMonitor]
        """

        if not self.node_sign or force_update:
            self.update_node_sign()

        # Add auth data
        fields['user_hash'] = self.cfg.user_hash
        fields['node_hash'] = self.cfg.node_hash
        fields['node_sign'] = self.node_sign

        e = encoder.MultipartEncoder(fields)
        m = encoder.MultipartEncoderMonitor(e, callback)
        return m

    def _create_request(self,
                        action,
                        server_addr,
                        data=(),
                        recurse_max=3,
                        enrich_data=True,
                        headers=()):
        data = {} if not data else data
        encoded = self.get_request_data(action, data) \
            if enrich_data else data

        try:
            response = self.make_post(url=server_addr,
                                      data=encoded,
                                      headers=headers)
            response = json.loads(response)
        except Exception as e:
            logger.error("Server response parsing failed with '%s'", e)
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, str(e))
            return None
        except:
            return None
        if 'result' not in response:
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, 'Result not in response')
            if recurse_max > 0:
                self.update_node_sign()
                return self._create_request(action=action,
                                            server_addr=server_addr,
                                            data=data,
                                            recurse_max=recurse_max - 1)
            else:
                return None
        if response['result'] == 'error':
            info = response.get('info', "")
            errcode = response.get('errcode', "")
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                error = 'info: {}, debug: {}'.format(info,
                                                     response.get('debug', ""))
                self._tracker.error(tb, error)
            if info == 'flst' and recurse_max > 0:
                self.update_node_sign()
                return self._create_request(action=action,
                                            server_addr=server_addr,
                                            data=data,
                                            recurse_max=recurse_max - 1)
            if errcode in ('SIGNATURE_INVALID', 'NODE_SIGN_NOT_FOUND') and \
                    recurse_max > 0:
                self.update_node_sign()
                return self._create_request(action=action,
                                            server_addr=server_addr,
                                            data=data,
                                            recurse_max=recurse_max - 1)
            if errcode == 'TIMEOUT':
                self.timeout_error.emit()
                return None
            else:
                return response
        return response

    def create_request(self, action, data=(), recurse_max=5):
        return self._create_request(action, self.server_addr, data,
                                    recurse_max)

    def create_event_request(self, action, data=(), recurse_max=3):
        return self._create_request(action, self.events_server_addr, data,
                                    recurse_max)

    def create_sharing_request(self, action, data=(), recurse_max=3):
        return self._create_request(action, self.sharing_server_addr, data,
                                    recurse_max)

    def create_upload_request(self, data, recurse_max=3):
        headers = {'Content-Type': data.content_type}
        return self._create_request("",
                                    self.upload_server_addr,
                                    data,
                                    recurse_max,
                                    enrich_data=False,
                                    headers=headers)

    def make_post(self, url, data, headers=()):
        try:
            logger.verbose("Sending POST request to '%s' with data '%s'", url,
                           data)
        except AttributeError:
            pass
        session = self._get_or_create_session(current_thread())
        try:
            kwargs = dict(timeout=(self.request_timeout, self.read_timeout))
            if headers:
                kwargs['headers'] = headers
            _res = session.post(url, data, **kwargs).text
            try:
                logger.verbose("Server replied: '%s'", _res)
            except AttributeError:
                pass
        except exceptions.Timeout as e:
            logger.error("Request failed due to timeout")
            _res = '{"result":"error","errcode":"TIMEOUT"}'
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, str(e))
        except Exception as e:
            logger.error("Request failed due to %s", e)
            _res = 'failure'
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, str(e))
        return _res

    def _get_or_create_session(self, thread):
        session = self._sessions.get(thread, None)
        if not session:
            session = Session()
            if self.cfg.host == REGULAR_URI:
                session.mount(self.cfg.host, SslPinningAdapter())
            self._sessions[thread] = session
        return session

    def sharing_enable(self, uuid, share_ttl):
        """
        'sharing_enable' request of share registration API.
        Registers file or folder sharing on API server

        @param uuid [str]
        @param share_ttl Time sharing be valid
            (in seconds) [int]
        @return (share_link [str], share_hash [str])
        @raise Client_APIError
        """

        data = {
            'uuid': uuid,
            'share_ttl': str(share_ttl),
            'share_password': None
        }
        response = self.create_sharing_request(action='sharing_enable',
                                               data=data)

        if response \
                and 'result' in response \
                and 'success' in response['result'] \
                and 'data' in response \
                and 'share_link' in response['data'] \
                and 'share_hash' in response['data']:
            share_link = str(response['data']['share_link'])
            share_hash = str(response['data']['share_hash'])
            return share_link, share_hash, ''
        elif response \
                and 'result' in response \
                and 'error' in response['result']:
            error_info = response.get('info', '')
            logger.error("Sharing failed, server response: '%s'", response)
            return None, None, error_info
        else:
            logger.error("Sharing failed, server response: '%s'", response)
            raise Client_APIError("Sharing failed", response)

    def sharing_disable(self, uuid):
        """
        'sharing_disable' request of share registration API.
        Registers file folder sharing cancelling on API server

        @param uuid [str]
        @raise Client_APIError
        """

        data = {
            'uuid': uuid,
        }
        response = self.create_sharing_request(action='sharing_disable',
                                               data=data)
        if not (response and 'result' in response
                and 'success' in response['result']):
            raise Client_APIError("Sharing failed", response)

    def file_event_create(self, event_uuid, file_name, file_size, folder_uuid,
                          diff_file_size, file_hash):
        '''
        'file_event_create' request of file event registration API.
        Registers file creation on API server

        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_name": ensure_unicode(file_name),
            "file_size": file_size,
            "folder_uuid": folder_uuid if folder_uuid else "",
            "diff_file_size": diff_file_size,
            "hash": file_hash,
        }

        return self.create_event_request(action='file_event_create', data=data)

    def file_event_update(self, event_uuid, file_uuid, file_size,
                          last_event_id, diff_file_size, rev_diff_file_size,
                          file_hash):
        '''
        'file_event_update' request of file event registration API.
        Registers file update on API server

        @param file_uuid UUID of file assigned on event creation [string]
        @param last_event_id Maximum ID of file event on the file being
            processed known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_uuid": file_uuid,
            "file_size": file_size,
            "last_event_id": str(last_event_id),
            "diff_file_size": diff_file_size,
            "rev_diff_file_size": rev_diff_file_size,
            "hash": file_hash,
        }
        return self.create_event_request(action='file_event_update', data=data)

    def file_event_delete(self, event_uuid, file_uuid, last_event_id):
        '''
        'file_event_delete' request of file event registration API.
        Registers file deletion on API server

        @param file_uuid UUID of file assigned on event creation [string]
        @param last_event_id Maximum ID of file event on the file being
            processed known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_uuid": file_uuid,
            "last_event_id": str(last_event_id)
        }
        return self.create_event_request(action='file_event_delete', data=data)

    def file_event_move(self, event_uuid, file_uuid, last_event_id,
                        new_file_name, new_folder_uuid):
        '''
        'file_event_move' request of file event registration API.
        Registers file moving on API server

        @param file_uuid UUID of file assigned on event creation [string]
        @param last_event_id Maximum ID of file event on the file being
            processed known to the node
        @param new_file_name New name of file [unicode]
        @param new_folder_uuid uuid of a folder where file will be placed
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_uuid": file_uuid,
            "new_folder_uuid": new_folder_uuid if new_folder_uuid else "",
            "new_file_name": new_file_name,
            "last_event_id": str(last_event_id)
        }
        return self.create_event_request(action='file_event_move', data=data)

    def folder_event_create(self, event_uuid, folder_name, parent_folder_uuid):

        if parent_folder_uuid is None:
            parent_folder_uuid = ""

        data = {
            "event_uuid": event_uuid,
            "folder_name": folder_name,
            "parent_folder_uuid": parent_folder_uuid,
        }
        return self.create_event_request(action='folder_event_create',
                                         data=data)

    def folder_event_move(self, event_uuid, folder_uuid, last_event_id,
                          new_folder_name, new_parent_folder_uuid):

        if new_parent_folder_uuid is None:
            new_parent_folder_uuid = ""

        data = {
            "event_uuid": event_uuid,
            "folder_uuid": folder_uuid,
            "new_folder_name": new_folder_name,
            "new_parent_folder_uuid": new_parent_folder_uuid,
            "last_event_id": str(last_event_id),
        }
        return self.create_event_request(action='folder_event_move', data=data)

    def folder_event_delete(self, event_uuid, folder_uuid, last_event_id):
        data = {
            "event_uuid": event_uuid,
            "folder_uuid": folder_uuid,
            "last_event_id": str(last_event_id),
        }
        return self.create_event_request(action='folder_event_delete',
                                         data=data)

    def file_event_get_filelist(self, last_event_id):
        '''
        'file_list' request of file event registration API.
        Obtains list of files created/updated after event with given ID

        @param last_event_id Maximum ID of file event known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {"last_event_id": str(last_event_id)}

        return self.create_event_request(action='file_list', data=data)

    def file_event_get_events(self, last_event_id):
        '''
        'file_events' request of file event registration API.
        Obtains events registered on server after event with given ID

        @param last_event_id Maximum ID of file event known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {"last_event_id": str(last_event_id)}

        return self.create_event_request(action='file_events', data=data)

    def start_http_download(self, upload_id):
        """
        'download' request for starting of uploaded file download via HTTP
        protocol. After node auth checking the server should return redirect to
        actual file URL

        @param upload_id ID of uploaded file [int]
        """

        data = {"upload_id": str(upload_id)}

        return self.create_event_request(action='download', data=data)

    def remote_action_done(self, remote_action_uuid):
        return self.create_request(action='remote_action_done',
                                   data=dict(action_uuid=remote_action_uuid))

    def patch_ready(self, patch_uuid, patch_size):
        return self.create_event_request(action='patch_ready',
                                         data=dict(diff_uuid=patch_uuid,
                                                   diff_size=patch_size))

    def get_token_login_link(self):
        return self.create_request(action='get_token_login_link')

    def node_management_action(self, action, node_id, action_type=""):
        if action_type:
            data = dict(target_node_id=node_id, action_type=action_type)
        else:
            data = dict(node_id=node_id)
        return self.create_request(action=action, data=data)

    def get_notifications(self, limit, from_id):
        data = {'from': from_id, 'limit': limit}
        return self.create_request(action="getNotifications", data=data)

    def accept_invitation(self, colleague_id):
        data = dict(colleague_id=colleague_id)
        return self.create_sharing_request(action="collaboration_join",
                                           data=data)

    def set_uris(self):
        self.server_addr = API_URI.format(self.cfg.host)
        self.events_server_addr = API_EVENTS_URI.format(self.cfg.host)
        self.sharing_server_addr = API_SHARING_URI.format(self.cfg.host)
        self.upload_server_addr = API_UPLOAD_URI.format(self.cfg.host)

    # Collaboration settings requests

    def collaboration_info(self, uuid):
        data = dict(uuid=uuid)
        return self.create_sharing_request(action="collaboration_info",
                                           data=data)

    def colleague_delete(self, uuid, colleague_id):
        data = dict(uuid=uuid, colleague_id=colleague_id)
        return self.create_sharing_request(action="colleague_delete",
                                           data=data)

    def colleague_edit(self, uuid, colleague_id, access_type):
        data = dict(uuid=uuid,
                    colleague_id=colleague_id,
                    access_type=access_type)
        return self.create_sharing_request(action="colleague_edit", data=data)

    def colleague_add(self, uuid, colleague_email, access_type):
        data = dict(uuid=uuid,
                    colleague_email=colleague_email,
                    access_type=access_type)
        return self.create_sharing_request(action="colleague_add", data=data)

    def collaboration_cancel(self, uuid):
        data = dict(uuid=uuid)
        return self.create_sharing_request(action="collaboration_cancel",
                                           data=data)

    def collaboration_leave(self, uuid):
        data = dict(uuid=uuid)
        return self.create_sharing_request(action="collaboration_leave",
                                           data=data)

    # Support requests

    def send_support_message(self, subject, body, log_file_name):
        data = dict(subject=subject, body=body)
        if log_file_name:
            data['log_file_name'] = log_file_name
        return self.create_request(action="support", data=data)

    def upload_file(self, path, mime_type, callback=lambda m: None):
        filename = op.basename(path)
        with open(path, 'rb') as f:
            fields = {'UploadLogsForm[uploadedFile]': (filename, f, mime_type)}
            data = self.get_upload_request_data(fields, callback)
            return self.create_upload_request(data)
Esempio n. 11
0
class TerminalWidget(QWidget):
    foreground_color_map = {
        0: "#000",
        1: "#b00",
        2: "#0b0",
        3: "#bb0",
        4: "#00b",
        5: "#b0b",
        6: "#0bb",
        7: "#bbb",
        8: "#666",
        9: "#f00",
        10: "#0f0",
        11: "#ff0",
        12: "#00f",  # concealed
        13: "#f0f",
        14: "#000",  # negative
        15: "#fff",  # default
    }
    background_color_map = {
        0: "#000",
        1: "#b00",
        2: "#0b0",
        3: "#bb0",
        4: "#00b",
        5: "#b0b",
        6: "#0bb",
        7: "#bbb",
        12: "#aaa",  # cursor
        14: "#000",  # default
        15: "#fff",  # negative
    }
    keymap = {
        Qt.Key_Backspace: chr(127),
        Qt.Key_Escape: chr(27),
        Qt.Key_AsciiTilde: "~~",
        Qt.Key_Up: "~A",
        Qt.Key_Down: "~B",
        Qt.Key_Left: "~D",
        Qt.Key_Right: "~C",
        Qt.Key_PageUp: "~1",
        Qt.Key_PageDown: "~2",
        Qt.Key_Home: "~H",
        Qt.Key_End: "~F",
        Qt.Key_Insert: "~3",
        Qt.Key_Delete: "~4",
        Qt.Key_F1: "~a",
        Qt.Key_F2: "~b",
        Qt.Key_F3: "~c",
        Qt.Key_F4: "~d",
        Qt.Key_F5: "~e",
        Qt.Key_F6: "~f",
        Qt.Key_F7: "~g",
        Qt.Key_F8: "~h",
        Qt.Key_F9: "~i",
        Qt.Key_F10: "~j",
        Qt.Key_F11: "~k",
        Qt.Key_F12: "~l",
    }

    session_closed = pyqtSignal()

    def __init__(self,
                 parent=None,
                 command="/bin/bash",
                 font_name="Monospace",
                 font_size=18):
        super(TerminalWidget, self).__init__(parent)
        self.parent().setTabOrder(self, self)
        self.setFocusPolicy(Qt.WheelFocus)
        self.setAutoFillBackground(False)
        self.setAttribute(Qt.WA_OpaquePaintEvent, True)
        self.setCursor(Qt.IBeamCursor)
        font = QFont(font_name)
        font.setPixelSize(font_size)
        self.setFont(font)
        self._session = None
        self._last_update = None
        self._screen = []
        self._text = []
        self._cursor_rect = None
        self._cursor_col = 0
        self._cursor_row = 0
        self._dirty = False
        self._blink = False
        self._press_pos = None
        self._selection = None
        self._clipboard = QApplication.clipboard()
        QApplication.instance().lastWindowClosed.connect(Session.close_all)
        if command:
            self.execute()

    def execute(self, command="/bin/bash"):
        self._session = Session()
        self._session.start(command)
        self._timer_id = None
        # start timer either with high or low priority
        if self.hasFocus():
            self.focusInEvent(None)
        else:
            self.focusOutEvent(None)

    def send(self, s):
        self._session.write(s)

    def stop(self):
        self._session.stop()

    def pid(self):
        return self._session.pid()

    def setFont(self, font):
        super(TerminalWidget, self).setFont(font)
        self._update_metrics()

    def focusNextPrevChild(self, next):
        if not self._session.is_alive():
            return True
        return False

    def focusInEvent(self, event):
        if not self._session.is_alive():
            return
        if self._timer_id is not None:
            self.killTimer(self._timer_id)
        self._timer_id = self.startTimer(250)
        self.update_screen()

    def focusOutEvent(self, event):
        if not self._session.is_alive():
            return
        # reduced update interval
        # -> slower screen updates
        # -> but less load on main app which results in better responsiveness
        if self._timer_id is not None:
            self.killTimer(self._timer_id)
        self._timer_id = self.startTimer(750)

    def resizeEvent(self, event):
        if not self._session.is_alive():
            return
        self._columns, self._rows = self._pixel2pos(self.width(),
                                                    self.height())
        self._session.resize(self._columns, self._rows)

    def closeEvent(self, event):
        if not self._session.is_alive():
            return
        self._session.close()

    def timerEvent(self, event):
        if not self._session.is_alive():
            if self._timer_id is not None:
                self.killTimer(self._timer_id)
                self._timer_id = None
            if DEBUG:
                print("Session closed")
            self.session_closed.emit()
            return
        last_change = self._session.last_change()
        if not last_change:
            return
        if not self._last_update or last_change > self._last_update:
            self._last_update = last_change
            old_screen = self._screen
            (self._cursor_col,
             self._cursor_row), self._screen = self._session.dump()
            self._update_cursor_rect()
            if old_screen != self._screen:
                self._dirty = True
        if self.hasFocus():
            self._blink = not self._blink
        self.update()

    def _update_metrics(self):
        fm = self.fontMetrics()
        self._char_height = fm.height()
        self._char_width = fm.width("W")

    def _update_cursor_rect(self):
        cx, cy = self._pos2pixel(self._cursor_col, self._cursor_row)
        self._cursor_rect = QRect(cx, cy, self._char_width, self._char_height)

    def _reset(self):
        self._update_metrics()
        self._update_cursor_rect()
        self.resizeEvent(None)
        self.update_screen()

    def update_screen(self):
        self._dirty = True
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        if self._dirty:
            self._dirty = False
            self._paint_screen(painter)
        else:
            if self._cursor_rect and not self._selection:
                self._paint_cursor(painter)
        if self._selection:
            self._paint_selection(painter)
            self._dirty = True

    def _pixel2pos(self, x, y):
        col = int(round(x / self._char_width))
        row = int(round(y / self._char_height))
        return col, row

    def _pos2pixel(self, col, row):
        x = col * self._char_width
        y = row * self._char_height
        return x, y

    def _paint_cursor(self, painter):
        if self._blink:
            color = "#aaa"
        else:
            color = "#fff"
        painter.setPen(QPen(QColor(color)))
        painter.drawRect(self._cursor_rect)

    def _paint_screen(self, painter):
        # Speed hacks: local name lookups are faster
        vars().update(QColor=QColor, QBrush=QBrush, QPen=QPen, QRect=QRect)
        background_color_map = self.background_color_map
        foreground_color_map = self.foreground_color_map
        char_width = self._char_width
        char_height = self._char_height
        painter_drawText = painter.drawText
        painter_fillRect = painter.fillRect
        painter_setPen = painter.setPen
        align = Qt.AlignTop | Qt.AlignLeft
        # set defaults
        background_color = background_color_map[14]
        foreground_color = foreground_color_map[15]
        brush = QBrush(QColor(background_color))
        painter_fillRect(self.rect(), brush)
        pen = QPen(QColor(foreground_color))
        painter_setPen(pen)
        y = 0
        text = []
        text_append = text.append
        for row, line in enumerate(self._screen):
            col = 0
            text_line = ""
            for item in line:
                if isinstance(item, str):
                    x = col * char_width
                    length = len(item)
                    rect = QRect(x, y, x + char_width * length,
                                 y + char_height)
                    painter_fillRect(rect, brush)
                    painter_drawText(rect, align, item)
                    col += length
                    text_line += item
                else:
                    foreground_color_idx, background_color_idx, underline_flag = item
                    foreground_color = foreground_color_map[
                        foreground_color_idx]
                    background_color = background_color_map[
                        background_color_idx]
                    pen = QPen(QColor(foreground_color))
                    brush = QBrush(QColor(background_color))
                    painter_setPen(pen)
                    # painter.setBrush(brush)
            y += char_height
            text_append(text_line)
        self._text = text

    def _paint_selection(self, painter):
        pcol = QColor(200, 200, 200, 50)
        pen = QPen(pcol)
        bcol = QColor(230, 230, 230, 50)
        brush = QBrush(bcol)
        painter.setPen(pen)
        painter.setBrush(brush)
        for (start_col, start_row, end_col, end_row) in self._selection:
            x, y = self._pos2pixel(start_col, start_row)
            width, height = self._pos2pixel(end_col - start_col,
                                            end_row - start_row)
            rect = QRect(x, y, width, height)
            # painter.drawRect(rect)
            painter.fillRect(rect, brush)

    def zoom_in(self):
        font = self.font()
        font.setPixelSize(font.pixelSize() + 2)
        self.setFont(font)
        self._reset()

    def zoom_out(self):
        font = self.font()
        font.setPixelSize(font.pixelSize() - 2)
        self.setFont(font)
        self._reset()

    return_pressed = pyqtSignal()

    def keyPressEvent(self, event):
        text = str(event.text())
        key = event.key()
        modifiers = event.modifiers()
        ctrl = modifiers == Qt.ControlModifier
        if ctrl and key == Qt.Key_Plus:
            self.zoom_in()
        elif ctrl and key == Qt.Key_Minus:
            self.zoom_out()
        else:
            if text and key != Qt.Key_Backspace:
                self.send(text.encode("utf-8"))
            else:
                s = self.keymap.get(key)
                if s:
                    self.send(s.encode("utf-8"))
                elif DEBUG:
                    print("Unknown key combination")
                    print("Modifiers:", modifiers)
                    print("Key:", key)
                    for name in dir(Qt):
                        if not name.startswith("Key_"):
                            continue
                        value = getattr(Qt, name)
                        if value == key:
                            print("Symbol: Qt.%s" % name)
                    print("Text: %r" % text)
        event.accept()
        if key in (Qt.Key_Enter, Qt.Key_Return):
            self.return_pressed.emit()

    def mousePressEvent(self, event):
        button = event.button()
        if button == Qt.RightButton:
            ctx_event = QContextMenuEvent(QContextMenuEvent.Mouse, event.pos())
            self.contextMenuEvent(ctx_event)
            self._press_pos = None
        elif button == Qt.LeftButton:
            self._press_pos = event.pos()
            self._selection = None
            self.update_screen()
        elif button == Qt.MiddleButton:
            self._press_pos = None
            self._selection = None
            text = str(self._clipboard.text(QClipboard.Selection))
            self.send(text.encode("utf-8"))
            # self.update_screen()

    def mouseReleaseEvent(self, QMouseEvent):
        pass  # self.update_screen()

    def _selection_rects(self, start_pos, end_pos):
        sx, sy = start_pos.x(), start_pos.y()
        start_col, start_row = self._pixel2pos(sx, sy)
        ex, ey = end_pos.x(), end_pos.y()
        end_col, end_row = self._pixel2pos(ex, ey)
        if start_row == end_row:
            if ey > sy or end_row == 0:
                end_row += 1
            else:
                end_row -= 1
        if start_col == end_col:
            if ex > sx or end_col == 0:
                end_col += 1
            else:
                end_col -= 1
        if start_row > end_row:
            start_row, end_row = end_row, start_row
        if start_col > end_col:
            start_col, end_col = end_col, start_col
        if end_row - start_row == 1:
            return [(start_col, start_row, end_col, end_row)]
        else:
            return [(start_col, start_row, self._columns, start_row + 1),
                    (0, start_row + 1, self._columns, end_row - 1),
                    (0, end_row - 1, end_col, end_row)]

    def text(self, rect=None):
        if rect is None:
            return "\n".join(self._text)
        else:
            text = []
            (start_col, start_row, end_col, end_row) = rect
            for row in range(start_row, end_row):
                text.append(self._text[row][start_col:end_col])
            return text

    def text_selection(self):
        text = []
        for (start_col, start_row, end_col, end_row) in self._selection:
            for row in range(start_row, end_row):
                text.append(self._text[row][start_col:end_col])
        return "\n".join(text)

    def column_count(self):
        return self._columns

    def row_count(self):
        return self._rows

    def mouseMoveEvent(self, event):
        if self._press_pos:
            move_pos = event.pos()
            self._selection = self._selection_rects(self._press_pos, move_pos)

            sel = self.text_selection()
            if DEBUG:
                print("%r copied to xselection" % sel)
            self._clipboard.setText(sel, QClipboard.Selection)

            self.update_screen()

    def mouseDoubleClickEvent(self, event):
        self._press_pos = None
        # double clicks create a selection for the word under the cursor
        pos = event.pos()
        x, y = pos.x(), pos.y()
        col, row = self._pixel2pos(x, y)
        line = self._text[row]
        # find start of word
        start_col = col
        found_left = 0
        while start_col > 0:
            char = line[start_col]
            if not char.isalnum() and char not in ("_", ):
                found_left = 1
                break
            start_col -= 1
        # find end of word
        end_col = col
        found_right = 0
        while end_col < self._columns:
            char = line[end_col]
            if not char.isalnum() and char not in ("_", ):
                found_right = 1
                break
            end_col += 1
        self._selection = [(start_col + found_left, row,
                            end_col - found_right + 1, row + 1)]

        sel = self.text_selection()
        if DEBUG:
            print("%r copied to xselection" % sel)
        self._clipboard.setText(sel, QClipboard.Selection)

        self.update_screen()

    def is_alive(self):
        return (self._session and self._session.is_alive()) or False
Esempio n. 12
0
class InterpolateIridium(QtCore.QObject):

    #Received Signals
    start = pyqtSignal()
    setInterrupt = pyqtSignal()
    setPredictionUpdateSpeed = pyqtSignal(float)

    def __init__(self, MainWindow):
        super(InterpolateIridium, self).__init__()
        self.mainWindow = MainWindow
        self.interpolateInterrupt = False

        self.ready = False

        #List holding all known updates
        self.balloonLocations = []

        #Panning Characteristics
        self.updateSpeed = 2  #in seconds
        self.latSpeed = 0  #in seconds
        self.lonSpeed = 0  #in seconds
        self.altSpeed = 0  #in seconds
        self.ticksSinceLastLocation = 0

        #Emitted Signals
        self.mainWindow.iridiumInterpolateNewLocation.connect(
            self.mainWindow.updateBalloonInterpolation)

    def run(self):
        """ Interpolates Iridium tracking information to provide smoother tracking """
        self.interpolateInterrupt = False

        while (not self.interpolateInterrupt):
            time.sleep(self.updateSpeed)  #update frequency

            if self.ready:
                print("Moving at " + "latSpeed: " + str(self.latSpeed) +
                      " lonSpeed: " + str(self.lonSpeed) + " altSpeed: " +
                      str(self.altSpeed))
                self.ticksSinceLastLocation += 1
                # Add speeds*ticks to last known location
                simulatedTime = self.balloonLocations[
                    len(self.balloonLocations) -
                    1].getTime()  #keep same timestamp -- meh?

                # Add last known location value + (speed per second) * (update speed in seconds) * ticks
                simulatedSeconds = self.balloonLocations[
                    len(self.balloonLocations) - 1].getSeconds() + (
                        self.updateSpeed * self.ticksSinceLastLocation
                    )  #Add updateSpeed * ticks for new seconds
                simulatedLat = self.balloonLocations[
                    len(self.balloonLocations) -
                    1].getLat() + (self.latSpeed * self.updateSpeed *
                                   self.ticksSinceLastLocation)
                simulatedLon = self.balloonLocations[
                    len(self.balloonLocations) -
                    1].getLon() + (self.lonSpeed * self.updateSpeed *
                                   self.ticksSinceLastLocation)
                simulatedAlt = self.balloonLocations[
                    len(self.balloonLocations) -
                    1].getAlt() + (self.altSpeed * self.updateSpeed *
                                   self.ticksSinceLastLocation)
                # Make new location object
                print("Simulated Location Update>>> " + "lat: " +
                      str(simulatedLat) + " lon: " + str(simulatedLon) +
                      " alt: " + str(simulatedAlt) + "\n")
                simulatedLocation = BalloonUpdate(
                    simulatedTime, simulatedSeconds, simulatedLat,
                    simulatedLon, simulatedAlt, "Iridium",
                    self.mainWindow.groundLat, self.mainWindow.groundLon,
                    self.mainWindow.groundAlt)
                # Notify main GUI of new location request
                self.mainWindow.iridiumInterpolateNewLocation.emit(
                    simulatedLocation)
            QCoreApplication.processEvents()

    def addPosition(self, update):
        # Make sure it's a good location
        # Don't consider updates with bad info to be new updates
        if ((update.getLat() == 0.0) or (update.getLon() == 0.0)
                or (update.getAlt() == 0.0)):
            return

        # Makes sure it's the newest location
        if len(self.balloonLocations) >= 1 and update.getSeconds(
        ) <= self.balloonLocations[len(self.balloonLocations) -
                                   1].getSeconds():
            return

        self.ticksSinceLastLocation = 0
        self.balloonLocations.append(update)
        if len(self.balloonLocations) >= 2:
            self.interpolate()

    def interpolate(self):
        print("-------NEW PREDICTION-------")
        #Simple prediction
        self.latDiff = self.balloonLocations[
            len(self.balloonLocations) -
            1].getLat() - self.balloonLocations[len(self.balloonLocations) -
                                                2].getLat()
        self.lonDiff = self.balloonLocations[
            len(self.balloonLocations) -
            1].getLon() - self.balloonLocations[len(self.balloonLocations) -
                                                2].getLon()
        self.altDiff = self.balloonLocations[
            len(self.balloonLocations) -
            1].getAlt() - self.balloonLocations[len(self.balloonLocations) -
                                                2].getAlt()
        self.timeDiff = self.balloonLocations[
            len(self.balloonLocations) -
            1].getSeconds() - self.balloonLocations[len(self.balloonLocations)
                                                    - 2].getSeconds()

        print("latDiff: " + str(self.latDiff))
        print("lonDiff: " + str(self.lonDiff))
        print("altDiff: " + str(self.altDiff))
        print("timeDiff : " + str(self.timeDiff) + "\n")
        print("----------------------------")
        self.latSpeed = self.latDiff / self.timeDiff
        self.lonSpeed = self.lonDiff / self.timeDiff
        self.altSpeed = self.altDiff / self.timeDiff

        self.ready = True

    def interrupt(self):
        self.ready = False
        self.interpolateInterrupt = True

    def setUpdateSpeed(self, speed):
        self.updateSpeed = speed
        print("Set update speed to: " + str(self.updateSpeed))
Esempio n. 13
0
class GuiFileList(QObject):
    FILE_LIST_ITEM_SIZE = 60
    FILE_LIST_ICON_SIZE = 48
    MAX_FILES = 7

    _icons_info_ready = pyqtSignal(list, list, set, set)

    def __init__(self, parent, service, config, dp):
        QObject.__init__(self, parent=parent)
        self._parent = parent
        self._service = service
        self._config = config
        self._dp = dp

        self._file_list_changing = False
        self._mime_icons = self._build_mime_icons()
        self._ui = self._parent.get_ui()
        self._files = dict()
        self._shared_files = set()

        self._ui.file_list.setFocusPolicy(Qt.NoFocus)
        self._ui.file_list.setAcceptDrops(True)
        self._ui.file_list.setFont(QFont('Nano', 10 * self._dp))
        self._ui.file_list.setGridSize(QSize(
            self.FILE_LIST_ITEM_SIZE, self.FILE_LIST_ITEM_SIZE))
        self._ui.file_list.setAutoScroll(False)
        self._ui.file_list.setUniformItemSizes(True)
        self._ui.file_list.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._ui.file_list.dropEvent = self._file_list_drop_event
        self._ui.file_list.dragEnterEvent = self._file_list_drag_enter_event
        self._ui.file_list.dragMoveEvent = self._file_list_drag_enter_event
        self._ui.welcome_label.setAcceptDrops(True)
        self._ui.welcome_label.dropEvent = self._file_list_drop_event
        self._ui.welcome_label.dragEnterEvent = self._file_list_drag_enter_event
        self._ui.welcome_label.dragMoveEvent = self._file_list_drag_enter_event

        self._icons_info_ready.connect(self._on_icons_info_ready)
        self._init_timers()

    def is_changing(self):
        return self._file_list_changing

    def has_files(self):
        return bool(self._files)

    def clear(self, clear_ui=True):
        self._files.clear()
        if clear_ui:
            self._ui.file_list.clear()

    def set_file_list(self, file_list):
        self._file_list_changing = True

        if not file_list:
            self._ui.file_list_views.setCurrentWidget(self._ui.welcome_label)
            self._ui.file_list.clear()
            self._files.clear()
            self._service.file_list_ready()
            self._file_list_changing = False
            return

        new_list = set(
            [FilePath(rel_path)
             for (rel_path, is_dir, created_time, was_updated) in file_list])
        old_list = set(self._files.keys())
        removed = old_list.difference(new_list)
        added = new_list

        self._get_icons_info(file_list, added, removed)

    def share_path_failed(self, paths):
        self._shared_files.difference_update(paths)

    def on_share_changed(self, shared):
        self._shared_files = set(shared)

    def on_file_moved(self, old_file, new_file):
        for path in self._shared_files.copy():
            if FilePath(path) in FilePath(old_file) or \
                    path == old_file:
                new_path = str(FilePath(op.join(new_file, op.relpath(path, old_file))))
                self._shared_files.discard(path)
                self._shared_files.add(new_path)

    def _init_timers(self):
        self._update_time_delta_timer = QTimer(self)
        self._update_time_delta_timer.setInterval(60 * 1000)
        self._update_time_delta_timer.timeout.connect(
            self._update_time_deltas)
        self._update_time_delta_timer.start()

        self._check_shared_timer = QTimer(self)
        self._check_shared_timer.setInterval(2 * 1000)
        self._check_shared_timer.timeout.connect(
            self._check_shared)
        self._check_shared_timer.start()


    def _is_shared(self, path):
        if path in self._shared_files:
            return True
        for shared_path in self._shared_files:
            if FilePath(path) in FilePath(shared_path):
                return True

        return False

    def _on_icons_info_ready(self, icons_info, file_list, added, removed):
        logger.verbose("File list %s, files %s, icons info %s",
                       file_list, self._files, icons_info)

        for rel_path in removed:
            self._files.pop(rel_path, None)

        if added:
            for i, file in enumerate(file_list):
                rel_path, is_dir, created_time, was_updated = file
                if rel_path in added:
                    try:
                        self._add_file_to_file_list(
                            i, rel_path, created_time, was_updated,
                            icons_info[i])
                    except IndexError:
                        logger.error(
                            "Index error in file list. "
                            "Index %s, Icons info: %s.",
                            i, self._icons_info)

        for i in range(len(self._files), self.MAX_FILES):
            item = self._ui.file_list.takeItem(i)
            if not item:
                break

            del item

        if self._ui.file_list_views.currentWidget() == self._ui.welcome_label:
            self._ui.file_list_views.setCurrentWidget(self._ui.file_list)

        self._service.file_list_ready()
        self._file_list_changing = False

    def _add_file_to_file_list(self, index, rel_path,
                               created_time, was_updated, icon_info):
        item = self._ui.file_list.item(index)
        if not item:
            item = QListWidgetItem()
            item.setFlags(item.flags() & ~Qt.ItemIsSelectable)
            item.setSizeHint(QSize(
                self.FILE_LIST_ITEM_SIZE, self.FILE_LIST_ITEM_SIZE))
            self._ui.file_list.insertItem(index, item)

        widget = self._ui.file_list.itemWidget(item)
        if not widget:
            widget = self._create_file_list_item_widget(
                rel_path, created_time, was_updated, icon_info)
            self._ui.file_list.setItemWidget(item, widget)
        else:
            self._update_file_list_item_widget(
                widget, rel_path, created_time, was_updated, icon_info)
        self._files[FilePath(rel_path)] = icon_info

    def _file_list_drop_event(self, event):
        data = event.mimeData()
        if data.hasUrls():
            event.acceptProposedAction()
            event.accept()
            urls = data.urls()
            if len(urls) == 1 and not data.text().startswith("file:///"):
                link = data.text()
                logger.verbose("Link dropped: %s", link)
                self.received_download_link.emit(link, None)
            else:
                dropped_files_or_folders = []
                for url in urls:
                    dropped_files_or_folders.append(url.toLocalFile())
                self._service.add_to_sync_folder(dropped_files_or_folders)
        else:
            event.ignore()

    def _file_list_drag_enter_event(self, e):
        if e.mimeData().hasUrls():
            e.accept()
        else:
            e.ignore()

    def _create_file_list_item_widget(self, rel_path, created_time,
                                      was_updated, icon_info):
        abs_path = op.join(self._config.sync_directory, rel_path)
        abs_path = ensure_unicode(abs_path)
        abs_path = FilePath(abs_path).longpath
        widget = QWidget(parent=self._ui.file_list)
        widget.created_time = created_time
        widget.was_updated = was_updated

        widget.mouseReleaseEvent = lambda _: \
            qt_reveal_file_in_file_manager(
                widget.get_link_button.abs_path)

        main_layout = QHBoxLayout(widget)

        icon_label = QLabel(widget)
        widget.icon_label = icon_label
        main_layout.addWidget(icon_label)

        vertical_layout = QVBoxLayout()
        main_layout.addLayout(vertical_layout)

        file_name_label = QLabel(widget)
        widget.file_name_label = file_name_label
        vertical_layout.addWidget(file_name_label)

        horizontal_layout = QHBoxLayout()
        vertical_layout.addLayout(horizontal_layout)

        time_delta_label = QLabel(widget)
        widget.time_delta_label = time_delta_label
        horizontal_layout.addWidget(time_delta_label, alignment=Qt.AlignTop)

        get_link_button = QPushButton(widget)
        widget.get_link_button = get_link_button
        horizontal_layout.addWidget(get_link_button, alignment=Qt.AlignTop)

        self._set_icon_label(icon_info, icon_label)

        file_name_label.setFixedSize(304, 24)
        file_name_label.setFont(QFont('Noto Sans', 10 * self._dp))
        file_name_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        file_name_label.setText(
            elided(rel_path, file_name_label))

        time_delta_label.setText(get_added_time_string(
            created_time, was_updated, False))
        time_delta_label.setFont(QFont('Noto Sans', 8 * self._dp, italic=True))
        time_delta_label.setMinimumSize(time_delta_label.width(), 24)
        time_delta_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        time_delta_label.setStyleSheet('color: #A792A9;')

        get_link_button.setText('   {}  '.format(tr('Get link')))
        get_link_button.setFlat(True)
        get_link_button.setChecked(True)
        get_link_button.setMinimumSize(120, 24)
        get_link_button.setFont(QFont("Noto Sans", 8 * self._dp, italic=True))
        get_link_button.setMouseTracking(True)
        self._setup_get_link_button(get_link_button, rel_path, abs_path)

        return widget

    def _update_file_list_item_widget(self, widget, rel_path, created_time,
                                      was_updated, icon_info):
        abs_path = op.join(self._config.sync_directory, rel_path)
        abs_path = ensure_unicode(abs_path)
        abs_path = FilePath(abs_path).longpath

        widget.created_time = created_time
        widget.was_updated = was_updated
        widget.file_name_label.setText(
            elided(rel_path, widget.file_name_label))
        widget.time_delta_label.setText(get_added_time_string(
            created_time, was_updated, False))

        self._set_icon_label(icon_info, widget.icon_label)
        get_link_button = widget.get_link_button
        get_link_button.rel_path = rel_path
        get_link_button.abs_path = abs_path
        if self._is_shared(rel_path):
            if not get_link_button.icon():
                get_link_button.setIcon(QIcon(':images/getLink.png'))
        elif get_link_button.icon():
            get_link_button.setIcon(QIcon())

    def _set_icon_label(self, icon_info, icon_label):
        image, mime, file_info = icon_info
        pixmap = None
        if image:
            pixmap = QPixmap(self.FILE_LIST_ICON_SIZE,
                             self.FILE_LIST_ICON_SIZE)
            pixmap.convertFromImage(image)
        elif mime:
            icon = self._get_icon(mime)
            if icon:
                pixmap = icon.pixmap(
                    self.FILE_LIST_ICON_SIZE, self.FILE_LIST_ICON_SIZE)
        if not pixmap or pixmap.isNull():
            icon = QFileIconProvider().icon(file_info)
            pixmap = icon.pixmap(
                self.FILE_LIST_ICON_SIZE, self.FILE_LIST_ICON_SIZE)
        if pixmap and not pixmap.isNull():
            icon_label.setPixmap(pixmap)
        icon_label.setScaledContents(True)
        icon_label.setFixedSize(
            self.FILE_LIST_ICON_SIZE, self.FILE_LIST_ICON_SIZE)
        icon_label.setAlignment(Qt.AlignCenter)

    def _get_icon(self, mime):
        if not mime:
            return None

        if mime in self._mime_icons:
            return self._mime_icons[mime]

        for key, icon in self._mime_icons.items():
            if mime.startswith(key):
                return icon

        return None

    @qt_run
    def _get_icons_info(self, file_list, added, removed):
        icons_info = []
        try:
            for path, is_dir, created_time, was_updated in file_list:
                if path not in added:
                    icons_info.append((None, None, None))
                    continue

                elif path in self._files:
                    icons_info.append(self._files[path])
                    continue

                abs_path = op.join(self._config.sync_directory, path)
                abs_path = ensure_unicode(abs_path)
                abs_path = FilePath(abs_path)           # .longpath doesn't work
                mime, _ = mimetypes.guess_type(path)
                image = QImage(abs_path)
                if image.isNull():
                    image = None
                icons_info.append((image, mime, QFileInfo(abs_path)))
        except Exception as e:
            logger.error("Icons info error: %s", e)

        self._icons_info_ready.emit(
            icons_info, file_list, added, removed)

    def _setup_get_link_button(self, get_link_button, rel_path, abs_path):
        get_link_button.setStyleSheet(
            'margin: 0;border: 0; text-align:right center;'
            'color: #A792A9;')
        get_link_button.setCursor(Qt.PointingHandCursor)
        get_link_button.rel_path = rel_path
        get_link_button.abs_path = abs_path
        if self._is_shared(rel_path):
            get_link_button.setIcon(QIcon(':images/getLink.png'))
        else:
            get_link_button.setIcon(QIcon())

        def enter(_):
            get_link_button.setStyleSheet(
                'margin: 0; border: 0; text-align:right center;'
                'color: #f9af61;')
            rel_path = get_link_button.rel_path
            if self._is_shared(rel_path):
                get_link_button.setIcon(
                    QIcon(':images/getLinkActive.png'))

        def leave(_):
            get_link_button.setStyleSheet(
                'margin: 0; border: 0; text-align:right center;'
                'color: #A792A9;')
            rel_path = get_link_button.rel_path
            if self._is_shared(rel_path):
                get_link_button.setIcon(
                    QIcon(':images/getLink.png'))

        get_link_button.enterEvent = enter
        get_link_button.leaveEvent = leave

        def get_link_button_clicked():
            get_link_button.setStyleSheet(
                'margin: 0; border: 0; text-align:right center;'
                'color: #f78d1e;')
            get_link_button.setIcon(
                QIcon(':images/getLinkClicked.png'))
            rel_path = get_link_button.rel_path
            self._shared_files.add(rel_path)
            abs_path = get_link_button.abs_path
            self._parent.share_path_requested.emit(abs_path)
        get_link_button.clicked.connect(get_link_button_clicked)

    def _build_mime_icons(self):
        icons = {
            'application/zip': 'archive.svg',
            'application/x-zip': 'archive.svg',
            'application/x-xz': 'archive.svg',
            'application/x-7z-compressed': 'archive.svg',
            'application/x-gzip': 'archive.svg',
            'application/x-tar': 'archive.svg',
            'application/x-bzip': 'archive.svg',
            'application/x-bzip2': 'archive.svg',
            'application/x-rar': 'archive.svg',
            'application/x-rar-compressed': 'archive.svg',
            'audio': 'music.svg',
            'video': 'video.svg',
            'application/vnd.oasis.opendocument.text': 'word.svg',
            'application/msword': 'word.svg',
            'application/vnd.oasis.opendocument.spreadsheet': 'excel.svg',
            'application/vnd.ms-excel': 'excel.svg',
        }

        result = {}
        for mime, icon_name in icons.items():
            result[mime] = QIcon(':/images/mime/' + icon_name)

        return result

    def _update_time_deltas(self):
        for row in range(len(self._files)):
            item = self._ui.file_list.item(row)
            if not item:
                break

            widget = self._ui.file_list.itemWidget(item)
            if not widget:
                break

            widget.time_delta_label.setText(get_added_time_string(
                widget.created_time, widget.was_updated, False))

    def _check_shared(self):
        for row in range(len(self._files)):
            item = self._ui.file_list.item(row)
            if not item:
                break

            widget = self._ui.file_list.itemWidget(item)
            if not widget:
                break

            get_link_button = widget.get_link_button
            if self._is_shared(get_link_button.rel_path):
                if not get_link_button.icon():
                    get_link_button.setIcon(QIcon(':images/getLink.png'))
            elif get_link_button.icon():
                get_link_button.setIcon(QIcon())
Esempio n. 14
0
class VLCStreamer(QtCore.QObject):

    # Received Signals
    start = pyqtSignal()
    kill = pyqtSignal()

    def __init__(self):
        super(VLCStreamer, self).__init__()
        StreamThread = None

    def startVLCStream(self):
        """ Executes the streaming command on the pi and then begins stream """
        try:
            print("Connecting to streaming pi")
            client = SSHClient()
            client.set_missing_host_key_policy(AutoAddPolicy)
            client.connect('192.168.1.69',
                           port=22,
                           username='******',
                           password='******')
            # If the pi is already streaming, will not start another streaming process
            client.exec_command(
                'if pgrep vlc; then echo "Streaming already started"; else ./vlcTest.sh; fi'
            )
        except Exception as e:
            print("Error sending commands to pi: ", str(e))

        # Delay to allow the streaming to start
        time.sleep(1)

        try:
            # Attempt to start streaming
            print("Starting VLC stream capture")
            timenow = datetime.datetime.now().strftime('%m-%w-%y_%H-%M-%S')
            print('Saving stream to StreamedVideo folder with name: ' +
                  timenow + '.mp4')
            self.saveStreamThread = threading.Thread(target=lambda: os.system(
                'vlc.exe rtsp://' + '192.168.1.69' + ':8080/ --sout=file/mp4:'
                + 'StreamedVideo\\'  # Folder
                + timenow + '.mp4'))  # Filename
            self.displayStreamThread = threading.Thread(
                target=lambda: os.system('vlc.exe rtsp://' + '192.168.1.69' +
                                         ':8080/'))

            self.saveStreamThread.start()
            time.sleep(.1)
            self.displayStreamThread.start()
        except Exception as e:
            print("Error beginning VLC Stream: ", str(e))

    def killVLCStream(self):
        """ Sends a command to the pi to kill streaming """
        try:
            print("Connecting to streaming pi")
            client = SSHClient()
            client.set_missing_host_key_policy(AutoAddPolicy)
            client.connect('192.168.1.69',
                           port=22,
                           username='******',
                           password='******')
            client.exec_command('pkill vlc')
            print("Killed pi's vlc stream")
        except Exception as e:
            print("Error sending commands to pi: ", str(e))
Esempio n. 15
0
class RfdCommand(QtCore.QObject):

    # Signals
    commandStart = pyqtSignal(str, str)
    commandInterrupt = pyqtSignal()
    piruntimeStart = pyqtSignal()
    statusStart = pyqtSignal()
    foundIdentifier = pyqtSignal(bool)

    def __init__(self, MainWindow, RFD):
        super(RfdCommand, self).__init__()
        self.rfdSer = RFD
        self.mainWindow = MainWindow
        self.acknowledged = False
        self.interrupt = False

        # Connections
        self.mainWindow.rfdCommandNewText.connect(
            self.mainWindow.updateRFDBrowser)
        self.mainWindow.commandFinished.connect(
            self.mainWindow.rfdCommandsDone)
        self.mainWindow.piruntimeFinished.connect(
            self.mainWindow.piruntimeDone)

    def command(self, identifier, command):
        """ Handles the RFD Commands """

        self.identifier = str(identifier)
        self.command = str(command)
        # Connect the identifier and the command with a ? separating for
        # parsing, and an ! at the end
        toSend = self.identifier + "?" + self.command + "!"

        # Send out the identifier so the listen function can get it
        self.rfdListen.shareIdentifier.emit(self.identifier)

        print(datetime.datetime.today().strftime('%H:%M:%S'))
        # Print out when the message began to send
        self.mainWindow.rfdCommandNewText.emit(
            '\n' + datetime.datetime.today().strftime('%H:%M:%S'))

        # Until the acknowledge is received, or the stop button is pressed,
        # keep sending the message ###
        self.setAcknowledged(False)
        self.setInterrupt(False)
        self.mainWindow.rfdCommandNewText.emit(
            "Sending " + toSend)  # Add the message to the browser
        while not self.acknowledged:
            QtGui.QApplication.processEvents()
            self.rfdSer.write(toSend)
            if self.interrupt:  # If the stop button is pressed, interrupt the sending
                self.mainWindow.rfdCommandNewText.emit("Command Interrupted")
                self.setAcknowledged(True)
            time.sleep(0.05)

        if self.interrupt:
            self.setInterrupt(False)

        else:
            print("Acknowledged at: " +
                  datetime.datetime.today().strftime('%H:%M:%S'))
            # Print out the time of acknowledge to see how long it took to get
            # the message through
            self.mainWindow.rfdCommandNewText.emit(
                "Acknowledged at: " +
                datetime.datetime.today().strftime('%H:%M:%S'))
            self.setAcknowledged(False)

        self.mainWindow.commandFinished.emit()

    def getPiRuntimeData(self):
        """ Retrieve the runtime data from the Pi """

        # Send the pi 7 until the acknowledge is received, or until too much
        # time has passed ###
        termtime = time.time() + 10
        timeCheck = time.time() + 1
        self.rfdSer.write('IMAGE;7!')
        while self.rfdSer.read() != 'A':
            if (timeCheck < time.time()):
                print("Waiting for Acknowledge")
                self.mainWindow.rfdCommandNewText.emit(
                    "Waiting for Acknowledge")
                timeCheck = time.time() + 1
            self.rfdSer.write('IMAGE;7!')
            if (termtime < time.time()):
                print("No Acknowldeg Received, Connection Error")
                self.mainWindow.rfdCommandNewText.emit(
                    "No Acknowledge Received, Connect Error")
                self.mainWindow.piruntimeFinished.emit()
                return

        ### Receive piruntimedata.txt ###
        timecheck = time.time()
        try:
            f = open("piruntimedata.txt", "w")
        except:
            print("Error opening file")
            self.mainWindow.rfdCommandNewText.emit("Error opening file")
            self.mainWindow.piruntimeFinished.emit()
            return
        timecheck = time.time()
        termtime = time.time() + 60
        temp = self.rfdSer.readline()
        while (temp != "\r") & (
                temp !=
                ""):  # Write everything the radio is receiving to the file
            f.write(temp)
            temp = self.rfdSer.read()
            if (termtime < time.time()):
                print("Error receiving piruntimedata.txt")
                self.mainWindow.rfdCommandNewText.emit(
                    "Error receiving piruntimedata.txt")
                f.close()
                self.mainWindow.piruntimeFinished.emit()
                return
        f.close()

        print("piruntimedata.txt saved to local folder")
        self.mainWindow.rfdCommandNewText.emit("piruntimedata.txt saved")
        print("Receive Time =", (time.time() - timecheck))
        self.mainWindow.rfdCommandNewText.emit("Receive Time =" +
                                               str((time.time() - timecheck)))

        ### Open piruntimedata.txt and print it into the command browser ###
        try:
            f = open('piruntimedata.txt', 'r')
            for line in f:
                print(line)
                self.mainWindow.rfdCommandNewText.emit(line)
            f.close()
        except:
            print("Error reading piruntimedata.txt")
            self.mainWindow.rfdCommandNewText.emit(
                "Error reading piruntimedata.txt")

        self.mainWindow.piruntimeFinished.emit()
        return

    def getDeviceStatus(self):
        """ Retrieve the status of the serial devices connected to the Pi """
        self.rfdSer.write('IMAGE;-!')

    def setAcknowledged(self, arg):
        self.acknowledged = arg

    def setInterrupt(self, arg):
        self.interrupt = arg

    def setListen(self, listen):
        self.rfdListen = listen
Esempio n. 16
0
class RfdListen(QtCore.QObject):

    # Received Signals
    listenStart = pyqtSignal()
    listenInterrupt = pyqtSignal()
    shareIdentifier = pyqtSignal(str)

    def __init__(self, MainWindow, RFD):
        super(RfdListen, self).__init__()
        self.rfdSer = RFD
        self.mainWindow = MainWindow
        self.interrupt = False
        self.identifier = ''

        # Emitted Signals
        self.mainWindow.rfdListenNewText.connect(
            self.mainWindow.updateRFDBrowser)
        self.mainWindow.rfdNewLocation.connect(
            self.mainWindow.updateBalloonLocation)
        self.mainWindow.payloadUpdate.connect(self.mainWindow.updatePayloads)

    def listen(self):
        """ Listens to the RFD serial port until interrupted """

        ### Loop until interrupted; handle anything received by the RFD ###
        self.rfdSer.flushInput()
        while not self.interrupt:
            QtGui.QApplication.processEvents()
            line = str(self.rfdSer.readline())
            # If the line received has the GPS identifier, handle it as a newly
            # received RFD balloon location update
            if line[0:3] == "GPS" and len(line[4:].split(',')) == 7:
                lineLst = line.split(',')
                lineLst[0] = lineLst[0][4:]
                lineLst[-1] = lineLst[-1][:-1]

                ### Interpret the balloon location list ###
                try:
                    hours = lineLst[0]  # Fix taken at this time
                    minutes = lineLst[1]  # Fix taken at this time
                    seconds = lineLst[2]  # Fix taken at this time
                    lat = stringToFloat(lineLst[3])  # Latitude in Degrees
                    lon = stringToFloat(lineLst[4])  # Longitude in Degrees
                    # Altitude in meters (sealevel)
                    alt = stringToFloat(lineLst[5])
                    # Number of Satellites
                    sat = stringToFloat(lineLst[6][:-1])
                except (Exception, e):
                    print(str(e))

                ### Do some calculations, get some values ###
                alt = alt * 3.2808  # Convert Altitude to feet
                gpsTime = hours + ":" + minutes + ":" + seconds.split(".")[0]
                rfdSeconds = stringToFloat(hours) * 3600 + stringToFloat(
                    minutes) * 60 + stringToFloat(seconds)

                ### Create a new location object ###
                try:
                    newLocation = BalloonUpdate(gpsTime, rfdSeconds, lat, lon,
                                                alt, "RFD",
                                                self.mainWindow.groundLat,
                                                self.mainWindow.groundLon,
                                                self.mainWindow.groundAlt)
                except (Exception, e):
                    print(str(e))

                try:
                    # Notify the main GUI of the new position
                    self.mainWindow.rfdNewLocation.emit(newLocation)
                except (Exception, e):
                    print(str(e))

                #self.mainWindow.rfdListenNewText.emit(datetime.datetime.today().strftime('%H:%M:%S') + " || "+line)

            if (line.replace('\n', '') == self.identifier
                    and self.identifier != ''):
                print('ID Found')
                self.rfdCommand.foundIdentifier.emit(True)
                self.identifier = ''

            elif line != '':  # Send the line to the text browser if it's not empty
                self.mainWindow.rfdListenNewText.emit(
                    datetime.datetime.today().strftime('%H:%M:%S') + " || " +
                    line)
                # Send it to the payload manager
                self.mainWindow.payloadUpdate.emit(line)

        self.interrupt = False

    def setInterrupt(self, arg):
        self.mainWindow.rfdCommandNewText.emit("Listen Interrupted")
        self.interrupt = arg

    def setIdentifier(self, ID):
        self.identifier = ID
        print("New ID: " + self.identifier)

    def setCommand(self, command):
        self.rfdCommand = command
Esempio n. 17
0
class SystemTrayIcon(QObject):
    clicked = pyqtSignal()
    double_clicked = pyqtSignal()

    def __init__(self, parent, menu, is_logging=False):
        QObject.__init__(self)

        def getIcon(name):
            return QIcon(':/images/tray_icons/' + name + '.png')

        self._icons = {
            STATUS_INIT: getIcon("disconnected") if is_logging \
                else getIcon("sync"),
            STATUS_DISCONNECTED: getIcon("disconnected"),
            STATUS_WAIT: getIcon("default"),
            STATUS_PAUSE: getIcon("pause"),
            STATUS_IN_WORK: getIcon("sync"),
            STATUS_INDEXING: getIcon("sync"),
        }
        self._statuses = {
            STATUS_INIT: tr("Pvtbox"),
            STATUS_DISCONNECTED: tr("Pvtbox connecting..."),
            STATUS_WAIT: tr("Pvtbox"),
            STATUS_PAUSE: tr("Pvtbox paused"),
            STATUS_IN_WORK: tr("Pvtbox syncing..."),
            STATUS_INDEXING: tr("Pvtbox indexing...")
        }
        self._tray = QSystemTrayIcon(self._icons[STATUS_INIT], parent)
        self.set_tool_tip(self._statuses[STATUS_INIT])
        self._tray.setContextMenu(menu)
        menu.aboutToShow.connect(self.clicked.emit)

        self._tray.activated.connect(self._on_activated)
        self._tray.installEventFilter(self)
        self._tray.setVisible(True)
        self._tray.show()

        self._tray_show_timer = QTimer(self)
        self._tray_show_timer.setInterval(3000)
        self._tray_show_timer.setSingleShot(False)
        self._tray_show_timer.timeout.connect(self.show)

    def eventFilter(self, obj, ev):
        if ev.type() == QEvent.ToolTip:
            self.clicked.emit()
        return False

    def _on_activated(self, reason):
        '''
        Slot for system tray icon activated signal.
        See http://doc.qt.io/qt-5/qsystemtrayicon.html

        @param reason Tray activation reason
        '''

        if reason == QSystemTrayIcon.Trigger:
            # This is usually when left mouse button clicked on tray icon
            self.clicked.emit()
        elif reason == QSystemTrayIcon.DoubleClick:
            self.double_clicked.emit()

    @property
    def menu(self):
        return self._tray.contextMenu()

    def set_tool_tip(self, tip):
        self._tray.setToolTip(tip)

    def show_tray_notification(self, text, title=""):
        if not title:
            title = tr('Pvtbox')
        # Convert strings to unicode
        if type(text) in (str, str):
            text = ensure_unicode(text)
        if type(title) in (str, str):
            title = ensure_unicode(title)

        logger.info("show_tray_notification: %s, title: %s", text, title)
        if self._tray.supportsMessages():
            self._tray.showMessage(title, text)
        else:
            logger.warning("tray does not supports messages")

    def request_to_user(
            self, dialog_id, text, buttons=("Yes", "No"), title="",
            close_button_index=-1, close_button_off=False, parent=None,
            on_clicked_cb=None,
            details=''):

        msg_box = QMessageBox(parent)
        # msg_box = QMessageBox()
        if not title:
            title = tr('Pvtbox')
        msg_box.setWindowTitle(title)
        msg_box.setText(str(text))

        pvtboxIcon = QIcon(':/images/icon.png')
        msg_box.setWindowIcon(pvtboxIcon)

        if details:
            msg_box.setDetailedText(details)

        if close_button_off:
            if get_platform() == 'Darwin':
                msg_box.setWindowFlags(Qt.Tool)
            else:
                msg_box.setWindowFlags(Qt.Dialog |
                                       Qt.CustomizeWindowHint |
                                       Qt.WindowTitleHint)
        msg_box.setAttribute(Qt.WA_MacFrameworkScaled)
        msg_box.setModal(True)

        buttons = list(buttons)
        for button in buttons:
            msg_box.addButton(button, QMessageBox.ActionRole)
        msg_box.show()
        msg_box.raise_()
        msg_box.exec_()
        try:
            button_index = buttons.index(msg_box.clickedButton().text())
        except (ValueError, AttributeError):
            button_index = -1
           # message box was closed with close button
            if len(buttons) == 1 and close_button_index == -1:
                # if only one button then call callback
                close_button_index = 0

            if len(buttons) > close_button_index >= 0:
                button_index = close_button_index

        if button_index >= 0 and callable(on_clicked_cb):
            on_clicked_cb(dialog_id, button_index)

    def update_status_icon(self, new_status, new_substatus):
        icon = self._icons[STATUS_DISCONNECTED] if new_status == STATUS_INIT \
            else self._icons[new_status]
        self._tray.setIcon(icon)
        tool_tip = self._statuses[new_status]
        if new_status == STATUS_IN_WORK and new_substatus == SUBSTATUS_SHARE:
            tool_tip = tr("Pvtbox downloading share...")
        self.set_tool_tip(tool_tip)
        self.show()

    def show(self):
        self._tray.setVisible(True)
        self._tray.show()

    def hide(self):
        if self._tray_show_timer.isActive():
            self._tray_show_timer.stop()
        self._tray.hide()

    def __del__(self):
        self._tray.removeEventFilter(self)
        self._tray.activated.disconnect()
        self.hide()
Esempio n. 18
0
class SyncDirMigration(QObject):
    progress = pyqtSignal(int)
    done = pyqtSignal()
    failed = pyqtSignal(str)

    def __init__(self, cfg, parent=None):
        QObject.__init__(self, parent=parent)

        self._cancelled = Event()
        self._cfg = cfg

    def cancel(self):
        logger.debug("Migration cancelled")
        self._cancelled.set()

    @qt_run
    def migrate(self, old_dir, new_dir):
        logger.info("Starting sync dir migration from %s, to %s",
                    old_dir, new_dir)
        old_dir = FilePath(old_dir).longpath
        new_dir = FilePath(new_dir).longpath
        old_files = get_filelist(old_dir)
        old_dirs = get_dir_list(old_dir)
        total_count = len(old_files) + len(old_dirs) + 1
        progress = 0
        sent_progress = 0
        logger.debug("Migration progress: %s/%s (%s%%)", 0, total_count, sent_progress)
        count = 1

        copied_dirs = []
        copied_files = []

        make_dirs(new_dir, is_folder=True)
        copied_dirs.append(new_dir)
        logger.debug("Migration progress: %s/%s (%s%%)", count, total_count, sent_progress)
        self.progress.emit(sent_progress)

        for dir in old_dirs:
            if self._cancelled.isSet():
                self._delete(dirs=copied_dirs)
                logger.debug("Migration done because cancelled")
                self.done.emit()
                return

            new_dir_path = ensure_unicode(op.join(
                new_dir, op.relpath(dir, start=old_dir)))

            try:
                make_dirs(new_dir_path, is_folder=True)
            except Exception as e:
                logger.error("Make dirs error: %s", e)
                self.failed.emit(str(e))
                self._delete(dirs=copied_dirs)
                return

            copied_dirs.append(new_dir_path)
            count += 1
            progress = int(count / total_count * 100)
            if progress > sent_progress:
                sent_progress = progress
                self.progress.emit(sent_progress)
            logger.debug("Migration progress: %s/%s (%s%%)", count, total_count, sent_progress)

        for file in old_files:
            if self._cancelled.isSet():
                self._delete(dirs=copied_dirs, files=copied_files)
                logger.debug("Migration done because cancelled")
                self.done.emit()
                return

            if file in HIDDEN_FILES:
                continue

            new_file_path = ensure_unicode(op.join(
                new_dir, op.relpath(file, start=old_dir)))

            logger.info("Copying file %s, to %s",
                        file, new_file_path)
            try:
                copy_file(file, new_file_path, preserve_file_date=True)
            except Exception as e:
                logger.error("Copy file error: %s", e)
                self.failed.emit(str(e))
                self._delete(dirs=copied_dirs, files=copied_files)
                return

            copied_files.append(new_file_path)
            count += 1
            progress = int(count / total_count * 100)
            if progress > sent_progress:
                sent_progress = progress
                self.progress.emit(sent_progress)
            logger.debug("Migration progress: %s/%s (%s%%)", count, total_count, sent_progress)

        logger.debug("Saving new config")
        self._cfg.set_settings(dict(sync_directory=FilePath(new_dir)))
        self._cfg.sync()
        logger.info("New config saved")

        logger.debug("Updating shortcuts")
        create_shortcuts(new_dir)
        remove_shortcuts(old_dir)
        logger.debug("Resetting custom folder icons")
        reset_all_custom_folder_icons(old_dir)

        logger.debug("Migration done")
        self.done.emit()

        logger.info("Migration thread end")

    def _delete(self, dirs=[], files=[]):
        for dir in dirs:
            remove_dir(dir)
        for file in files:
            remove_file(file)
Esempio n. 19
0
class StillImageSystem(QtCore.QObject):
    """ A class for controlling the still image system """

    # Signals
    mostRecentImageStart = pyqtSignal(str)
    imageDataStart = pyqtSignal()
    requestedImageStart = pyqtSignal(str)
    getSettingsStart = pyqtSignal()
    sendSettingsStart = pyqtSignal(list)
    vFlipStart = pyqtSignal()
    hFlipStart = pyqtSignal()
    timeSyncStart = pyqtSignal()
    stillInterrupt = pyqtSignal()

    def __init__(self, MainWindow, RFD):
        super(StillImageSystem, self).__init__()
        self.rfdSer = RFD
        self.interrupt = False
        self.mainWindow = MainWindow

        # Picture Qualities
        self.picWidth = 650
        self.picHeight = 450
        self.picSharpness = 0
        self.picBrightness = 50
        self.picContrast = 0
        self.picSaturation = 0
        self.picISO = 400

        # Variable to determine spacing of checksum. Ex. wordlength = 1000 will
        # send one thousand bits before calculating and verifying checksum
        self.wordlength = 8000
        self.extension = ".jpg"
        # The starting display photo is the logo of the MnSGC
        self.displayPhotoPath = "Images/MnSGC_Logo_highRes.png"

        self.mainWindow.stillNewText.connect(
            self.mainWindow.updateStillBrowser)
        self.mainWindow.listboxUpdate.connect(self.mainWindow.updateListbox)
        self.mainWindow.stillNewProgress.connect(
            self.mainWindow.updatePictureProgress)
        self.mainWindow.newPicture.connect(self.mainWindow.updatePicture)
        self.mainWindow.requestConfirmation.connect(
            self.mainWindow.checkRequestedImage)
        self.mainWindow.newPicSliderValues.connect(
            self.mainWindow.updateStillImageValues)
        self.mainWindow.stillSystemFinished.connect(
            self.mainWindow.stillImageSystemFinished)

    def getMostRecentImage(self, requestedImageName):
        """ Still Image System: Get the Most Recent Image through the RFD 900 """
        print('entered')

        ### Write 1 until you get the acknowledge back ###
        self.rfdSer.write(b'1!')
        timeCheck = time.time() + 1
        killTime = time.time() + 10
        while self.rfdSer.read() != b'A':
            if timeCheck < time.time():			# Make sure you don't print out a huge stream if you get the wrong response
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            sys.stdout.flush()
            #self.rfdSer.write(b'1!') #probably breaks code dunno ? :/ uwu

        ### Make the file name by reading the radio ###
        sendfilename = ""
        temp = 0
        while(temp <= 14):
            sendfilename += str(self.rfdSer.read())
            temp += 1

        # Get the image path name from the entry box, or create it if there's
        # nothing entered ###
        imagepath = requestedImageName
        if (imagepath == ""):
            try:
                if sendfilename[0] == "i":
                    imagepath = sendfilename
                else:
                    imagepath = "image_%s%s" % (
                        str(datetime.datetime.today().strftime("%Y%m%d_T%H%M%S")), self.extension)
            except:
                imagepath = "image_%s%s" % (
                    str(datetime.datetime.today().strftime("%Y%m%d_T%H%M%S")), self.extension)
        else:
            imagepath = imagepath + self.extension
        print("Image will be saved as:", imagepath)
        self.mainWindow.stillNewText.emit(
            "Image will be saved as: " + imagepath)

        ### Receive the Image ###
        timecheck = time.time()
        sys.stdout.flush()
        self.receive_image(str(imagepath), self.wordlength)			# Get the picture
        print("Receive Time =", (time.time() - timecheck))
        self.mainWindow.stillNewText.emit(
            "Receive Time = " + str((time.time() - timecheck)))

        ### Clean Up and Exit ###
        self.mainWindow.stillNewProgress.emit(
            0, 1)		# Reset the progress bar to empty
        sys.stdout.flush()							# Clear the buffer

        self.mainWindow.stillSystemFinished.emit()			# Emit the finish signal

        return

    def getImageDataTxt(self):
        """ Still Image System: Requests imagedata.txt, for the purpose of selecting a specific image to download """

        ### Send the Pi 2 until the acknowledge is received ###
        self.rfdSer.write(b'2!')
        timeCheck = time.time() + 1
        time.sleep(1)
        while self.rfdSer.read() != b'A':
            if timeCheck < time.time():				# Make sure you don't print out a huge stream if the wrong thing is received
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            sys.stdout.flush()
            print("Test")
            self.rfdSer.write(b'2!')
            self.mainWindow.stillNewText.emit("Test")
            

        try:
            f = open('imagedata' + ".txt", "w")
            print("opened image")
        except:
            print("Error with Opening File")
            self.mainWindow.stillNewText.emit("Error with Opening File")
            sys.stdout.flush()

            self.mainWindow.stillSystemFinished.emit()

            return

        # Read each line that received from the RFD, and write them to the file
        # ###
        timecheck = time.time()
        temp = self.rfdSer.readline()
        while temp != 'X\n':
            f.write(temp)
            try:
                self.mainWindow.listboxUpdate.emit(temp)
            except:
                print("Error Adding Items")
                self.mainWindow.stillNewText.emit("Error Adding Items")
                break
            temp = self.rfdSer.readline()
        f.close()
        sys.stdout.flush()
        self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal

        return

    def getRequestedImage(self, data):
        """ Still Image System: Retrieves the image specified in the argument, deletes the confirmation window if needed """

        ### Continuously write 3 until the acknowledge is received ###
        self.rfdSer.write(b'3!')
        timeCheck = time.time() + 1
        killTime = time.time() + 10
        while self.rfdSer.read() != b'A' and time.time() < killTime:
            if timeCheck < time.time():			# Make sure you don't emit a huge stream of messages if the wrong this is received
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            sys.stdout.flush()
            #self.rfdSer.write('IMAGE;3!')
         #self.sync(self.rfdSer)
        # # Syncronize the data streams of the ground station and the Pi before
        # starting
        imagepath = data[0:15]
        self.rfdSer.write('B,')
        # Tell the pi which picture you want to download
        self.rfdSer.write(str(data))
        timecheck = time.time()
        print("Image will be saved as:", imagepath)
        self.mainWindow.stillNewText.emit(
            "Image will be saved as: " + str(imagepath))
        sys.stdout.flush()
        # Receive the image
        self.receive_image(str(imagepath), self.wordlength)
        print("Receive Time =", (time.time() - timecheck))
        self.mainWindow.stillNewText.emit(
            "Receive Time = " + str((time.time() - timecheck)))
        self.mainWindow.stillNewProgress.emit(0, 1)			# Reset the progress bar
        sys.stdout.flush()
        self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal

        return

    def getPicSettings(self):
        """ Still Image System: Retrieve Current Camera Settings """

        print("Retrieving Camera Settings")
        self.mainWindow.stillNewText.emit("Retrieving Camera Settings")
        killtime = time.time() + 10  			# A timeout for the loop so you don't get stuck

        ### Send the Pi 4 until the acknowledge is received ###
        self.rfdSer.write(b'4!')
        timeCheck = time.time()
        while (self.rfdSer.read() != b'A') & (time.time() < killtime):
            if time.time() < timeCheck:					# Make sure you don't print out a huge stream if you get the wrong response
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            #self.rfdSer.write('IMAGE;4!')

        if time.time() > killtime:
            self.mainWindow.stillNewText.emit('No Acknowledge Received')
            sys.stdout.flush()
            return

        termtime = time.time() + 10
        done = False
        while not done:
            settings = self.rfdSer.readline()
            print(settings)
            if not settings == '':
                settings = settings.replace(b'\n', b'')
                print(settings)
                settingsLst = settings.split(b',')
                print(settingsLst)
                fail = True
                if len(settingsLst) == 7:
                    fail = False
                    for each in settingsLst:
                        temp = each.replace(b'-', b'')
                        print(temp)
                        if not temp.isdigit():
                            fail = True
                if not fail:
                    try:
                        done = True
                        print(settingsLst)
                        self.picWidth = int(settingsLst[0])
                        self.picHeight = int(settingsLst[1])
                        self.picSharpness = int(settingsLst[2])
                        self.picBrightness = int(settingsLst[3])
                        self.picContrast = int(settingsLst[4])
                        self.picSaturation = int(settingsLst[5])
                        self.picISO = int(settingsLst[6]) 
                        self.mainWindow.stillNewText.emit(
                            "Width = " + str(self.picWidth))
                        self.mainWindow.stillNewText.emit(
                            "Height = " + str(self.picHeight))
                        self.mainWindow.stillNewText.emit(
                            "Sharpness = " + str(self.picSharpness))
                        self.mainWindow.stillNewText.emit(
                            "Brightness = " + str(self.picBrightness))
                        self.mainWindow.stillNewText.emit(
                            "Contrast = " + str(self.picContrast))
                        self.mainWindow.stillNewText.emit(
                            "Saturation = " + str(self.picSaturation))
                        self.mainWindow.stillNewText.emit(
                            "ISO = " + str(self.picISO) + '\n')
                        self.mainWindow.newPicSliderValues.emit(
                            [self.picWidth, self.picHeight, self.picSharpness, self.picBrightness, self.picContrast, self.picSaturation, self.picISO])
                    except:
                        self.mainWindow.stillNewText.emit(
                            'Error retrieving Camera Settings')
            if time.time() > termtime:
                #done = True
                self.mainWindow.stillNewText.emit('Failed to Receive Settings')

        ## Open the file camerasettings.txt in write mode, and write everything the Pi is sending ###
        # try:
            # file = open("camerasettings.txt","w")
            # print "File Successfully Created"
            # self.mainWindow.stillNewText.emit("File Successfully Created")
        # except:				# If there's an error opening the file, print the message and return
            # print "Error with Opening File"
            # self.mainWindow.stillNewText.emit("Error with Opening File")
            # sys.stdout.flush()
            # self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal
            # return
        # timecheck = time.time()
        # sys.stdin.flush()				# Clear the buffer
        # temp = self.rfdSer.read()
        # while (temp != "\r") & (temp != ""):		# Write everything the radio is receiving to the file
            # file.write(temp)
            # temp = self.rfdSer.read()
        # file.close()
        # print "Receive Time =", (time.time() - timecheck)
        # self.mainWindow.stillNewText.emit("Receive Time = " + str((time.time() - timecheck)))
        # sys.stdout.flush()

        ## Open the file camerasettings.txt in read mode, and confirm/set the globals based on what's in the settings file ###
        # try:
            # file = open("camerasettings.txt","r")
            # twidth = file.readline()			 # Default = (650,450); range up to
            # self.picWidth = int(twidth)
            # print("Width = " + str(self.picWidth))
            # self.mainWindow.stillNewText.emit("Width = " + str(self.picWidth))
            # theight = file.readline()			 # Default = (650,450); range up to
            # self.picHeight = int(theight)
            # print("Height = " + str(self.picHeight))
            # self.mainWindow.stillNewText.emit("Height = " + str(self.picHeight))
            # tsharpness = file.readline()			  # Default  =0; range = (-100 to 100)
            # self.picSharpness = int(tsharpness)
            # print("Sharpness = " + str(self.picSharpness))
            # self.mainWindow.stillNewText.emit("Sharpness = " + str(self.picSharpness))
            # tbrightness = file.readline()			 # Default = 50; range = (0 to 100)
            # self.picBrightness = int(tbrightness)
            # print("Brightness = " + str(self.picBrightness))
            # self.mainWindow.stillNewText.emit("Brightness = " + str(self.picBrightness))
            # tcontrast = file.readline()			   # Default = 0; range = (-100 to 100)
            # self.picContrast = int(tcontrast)
            # print("Contrast = " + str(self.picContrast))
            # self.mainWindow.stillNewText.emit("Contrast = " + str(self.picContrast))
            # tsaturation = file.readline()			 # Default = 0; range = (-100 to 100)
            # self.picSaturation = int(tsaturation)
            # print("Saturation = " + str(self.picSaturation))
            # self.mainWindow.stillNewText.emit("Saturation = " + str(self.picSaturation))
            # tiso = file.readline()					  # Unknown Default; range = (100 to 800)
            # self.picISO = int(tiso)
            # print("ISO = " + str(self.picISO))
            # self.mainWindow.stillNewText.emit("ISO = " + str(self.picISO))
            # file.close()
            # self.mainWindow.newPicSliderValues.emit([self.picWidth,self.picHeight,self.picSharpness,self.picBrightness,self.picContrast,self.picSaturation,self.picISO])
        # except Exception, e:
            # print(str(e))
            # print "Camera Setting Retrieval Error"
            # self.mainWindow.stillNewText.emit("Camera Setting Retrieval Error")
        sys.stdout.flush()
        self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal

        return

    def sendNewPicSettings(self, settings):
        """ Still Image System: Send New Camera Settings to the Pi """

        # Update the instance variables
        self.picWidth = int(settings[0])
        self.picHeight = int(settings[1])
        self.picSharpness = int(settings[2])
        self.picBrightness = int(settings[3])
        self.picContrast = int(settings[4])
        self.picSaturation = int(settings[5])
        self.picISO = int(settings[6])

        ## Open the camerasettings.txt file, and record the new values ###
        # file = open("camerasettings.txt","w")
        # file.write(str(self.picWidth)+"\n")
        # file.write(str(self.picHeight)+"\n")
        # file.write(str(self.picSharpness)+"\n")
        # file.write(str(self.picBrightness)+"\n")
        # file.write(str(self.picContrast)+"\n")
        # file.write(str(self.picSaturation)+"\n")
        # file.write(str(self.picISO)+"\n")
        # file.close()

        # Continue sending 5 until the acknowledge is received from the Pi ###
        self.rfdSer.write(b'5!')
        acknowledge = self.rfdSer.read()
        timeCheck = time.time() + 1
        termtime = time.time() + 10
        while acknowledge != b'A' and time.time() < termtime:
            acknowledge = self.rfdSer.read()
            print(acknowledge)
            if timeCheck < time.time():
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            #self.rfdSer.write('IMAGE;5!')
            timecheck = time.time()

        if time.time() > termtime:
            self.mainWindow.stillNewText.emit(
                'No Acknowledge Received, Settings not Updated')
            return

        termtime = time.time() + 10
        settingsStr = str(self.picWidth) + ',' + str(self.picHeight) + ',' + str(self.picSharpness) + ',' + str(
            self.picBrightness) + ',' + str(self.picContrast) + ',' + str(self.picSaturation) + ',' + str(self.picISO)
        while self.rfdSer.read() != b'B' and time.time() < termtime:
            if timeCheck < time.time():
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            #print(b'A/' + settingsStr.encode('utf-8') + b'\n')
            self.rfdSer.write(b'A/' + settingsStr.encode('utf-8') + b'\n')
            self.rfdSer.flushOutput()

        if time.time() > termtime:
            self.mainWindow.stillNewText.emit(
                'No Acknowledge Received on Settings Update\n')
        else:
            localtime = str(
            datetime.datetime.today().strftime("%H:%M:%S"))
            self.mainWindow.stillNewText.emit('Settings Updated @ %s\n' % (localtime))
            print('\nSettings Updated @ %s\n' % (localtime))
        ## Open the camerasettings.txt file in read mode, and send each line to the Pi ###
        # try:
            # file = open("camerasettings.txt","r")
        # except:
            # print "Error with Opening File"
            # self.mainWindow.stillNewText.emit("Error with Opening File")
            # sys.stdout.flush()
            # self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal
            # return
        # timecheck = time.time()
        # temp = file.readline()
        # time.sleep(0.5)
        # self.rfdSer.write('B')
        # while temp != "":
            # print(temp)
            # self.rfdSer.write(temp)
            # temp = file.readline()
        # file.close()

        ## Look for an Acknowledge ###
        # error = time.time()
        # while self.rfdSer.read() != b'A':			# Make sure you don't print out a huge stream if you get the wrong response
            # if timeCheck < time.time():
            # print "Waiting for Acknowledge"
            # self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
            # timeCheck = time.time() + 1
            # sys.stdout.flush()
            # if error+10<time.time():
            # print "Acknowledge not Received"
            # self.mainWindow.stillNewText.emit("Acknowledge not Received")
            # self.mainWindow.stillSystemFinished.emit()
            # return
        # print "Send Time =", (time.time() - timecheck)
        # self.mainWindow.stillNewText.emit("Send Time =" + str((time.time() - timecheck)))

        sys.stdout.flush()			# Clear the buffer

        self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal
        return

    def picVerticalFlip(self):
        """ Still Image System: Flips the image vertically """

        # Send the pi 0 until the acknowledge is received, or until too much
        # time has passed ###
        self.rfdSer.write(b'0!')
        termtime = time.time() + 10
        timeCheck = time.time() + 1
        while self.rfdSer.read() != b'A':
            # self.rfdSer.write('IMAGE;0!')
            # time.sleep(1)
            if timeCheck < time.time():
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            # self.rfdSer.write('IMAGE;0!')
            if termtime < time.time():
                print("No Acknowldeg Received, Connection Error")
                self.mainWindow.stillNewText.emit(
                    "No Acknowledge Received, Connect Error")
                sys.stdout.flush()

                self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal
                return

        print("Camera Vertically Flipped")
        self.mainWindow.stillNewText.emit("Camera Vertically Flipped")
        sys.stdout.flush()
        self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal

    def picHorizontalFlip(self):
        """ Still Image System: Flips the image Horizontally """

        # Send the pi 9 until the acknowledge is received, or until too much
        # time has passed ###
        self.rfdSer.write(b'9!')
        termtime = time.time() + 10
        timeCheck = time.time() + 1
        while self.rfdSer.read() != b'A':
            if timeCheck < time.time():
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            # self.rfdSer.write('IMAGE;9!')
            if termtime < time.time():
                print("No Acknowldeg Received, Connection Error")
                self.mainWindow.stillNewText.emit(
                    "No Acknowledge Received, Connect Error")
                sys.stdout.flush()

                self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal
                return

        print("Camera Horizontally Flipped")
        self.mainWindow.stillNewText.emit("Camera Horizontally Flipped")
        sys.stdout.flush()
        self.mainWindow.stillSystemFinished.emit()		# Emit the finished signal

    def time_sync(self):
        """ Still Image System: Syncronizes the Pi and ground station so that the connection test can be run """

        # Send the Pi 8 until the acknowledge is received, or until the too
        # much time has passed ###
        self.rfdSer.write(b'8!')
        termtime = time.time() + 20
        timeCheck = time.time() + 1
        while self.rfdSer.read() != b'A':
            if timeCheck < time.time():
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            #self.rfdSer.write('8!')
            if termtime < time.time():  # If too much time has passed, let the user know and return
                print("No Acknowledge Received, Connection Error")
                self.mainWindow.stillNewText.emit(
                    'No Acknowledge Received, Connection Error\n')
                sys.stdout.flush()

                self.mainWindow.stillSystemFinished.emit() 		# Emit the finished signal
                return

        ### Display the time on the Pi and the local time ###
        localtime = str(
            datetime.datetime.today().strftime("%m/%d/%Y %H:%M:%S"))
        rasptime = str(self.rfdSer.readline())
        print("##################################\nRaspb Time = %s\nLocal Time = %s\n##################################" % (rasptime, localtime))
        self.mainWindow.stillNewText.emit(
            "##################################\nRaspb Time = %s\nLocal Time = %s\n##################################" % (rasptime, localtime) + '\n')
            #sys.stdin.flush()

        # Run the connection test
        self.connectiontest(10)
        sys.stdout.flush()
        self.mainWindow.stillSystemFinished.emit() 		# Emit the finished signal
        return

    def receive_image(self, savepath, wordlength):
        """ Receive an Image through the RFD 900 """
        # Notifies User we have entered the receiveimage() module
        print("Confirmed photo request")
        self.mainWindow.stillNewText.emit("Confirmed photo request")
        sys.stdout.flush()


        print("starting wordlength: " + str(wordlength))
        ### Module Specific Variables ###
        # Initializes the checksum timeout (timeout value is not set here)
        scount = 0
        trycnt = 0
        failcount = 0
        totalcnt = 0
        finalstring = ''		# Initializes the data string so that the += function can be used
        done = False			# Initializes the end condition

        ### Setup the Progress Bar ###
        stillProgress = 0
        try:
            # The first thing you get is the total picture size so you can make
            # the progress bar
            time.sleep(1)
            photoSize = self.rfdSer.readline()
            print("Total Picture Size: ", photoSize)
            self.mainWindow.stillNewText.emit(
                "Total Picture Size: " + str(int(photoSize)))
            stillPhotoMax = int(photoSize)
            print(stillPhotoMax)
            self.mainWindow.stillNewProgress.emit(stillProgress, stillPhotoMax)
        except:
            print("Error retrieving picture size")
            self.mainWindow.stillNewText.emit("Error retrieving picture size")
            stillPhotoMax = 1

        ### Retreive Data Loop (Will end when on timeout) ###
        while not done:
            print("Current Receive Position: ", str(len(finalstring)))
            self.mainWindow.stillNewText.emit(
                "Current Received Position: " + str(len(finalstring)))
            # Asks first for checksum. Checksum is asked for first so that if
            # data is less than wordlength, it won't error out the checksum
            # data
            checktheirs = self.rfdSer.read(32)
            print(checktheirs)
            # Retreives characters, who's total string length is predetermined
            # by variable wordlength
            word = self.rfdSer.read(wordlength)
            # Retreives a checksum based on the received data strings
            checkours = self.gen_checksum(word).encode('ascii')
            print(checkours)

            # CHECKSUM
            if checkours != checktheirs:
                if trycnt < 10:		# This line sets the maximum number of checksum resends. Ex. trycnt = 5 will attempt to rereceive data 5 times before erroring out											  #I've found that the main cause of checksum errors is a bit drop or add desync, this adds a 2 second delay and resyncs both systems
                    scount = 0
                    self.rfdSer.write(b'N')
                    trycnt += 1
                    failcount += 1
                    print("try number:", str(trycnt))
                    self.mainWindow.stillNewText.emit(
                        "try number: " + str(trycnt))
                    print("\tresend last")		# This line is mostly used for troubleshooting, allows user to view that both devices are at the same position when a checksum error occurs
                    self.mainWindow.stillNewText.emit("\tresent last")
                    print("\tpos @", str(len(finalstring)))
                    self.mainWindow.stillNewText.emit(
                        "\tpos @ " + str(len(finalstring)))
                    print("\twordlength", str(wordlength))
                    self.mainWindow.stillNewText.emit(
                        "\twordlength " + str(wordlength))
                    sys.stdout.flush()
##                    if wordlength > 1000:
##                        wordlength -= 1000
                    self.sync()		# This corrects for bit deficits or excesses ######  THIS IS A MUST FOR DATA TRANSMISSION WITH THE RFD900s!!!! #####
                else:
                    # Kind of a worst case, checksum trycnt is reached and so
                    # we save the image and end the receive, a partial image
                    # will render if enough data
                    self.rfdSer.write(b'N')
                    finalstring += word.decode('utf-8')
                    done = True
                    break
            else:							# If everything goes well, reset the try counter, and add the word to the accumulating final wor
                trycnt = 0
                scount += 1
                self.rfdSer.write(b'Y')
                finalstring += word.decode('utf-8')
                stillProgress += wordlength
                self.mainWindow.stillNewProgress.emit(
                    stillProgress, stillPhotoMax)
##                if(scount > 10 and wordlength < 7000):
##                    scount = 0
##                    wordlength += 1000
##                    print("\nIncreasing Wordsize : +", str(wordlength))
##                    print("\n")
##                    self.mainWindow.stillNewText.emit("\nIncreasing Wordsize :" + str(wordlength))
##                    self.mainWindow.stillNewText.emit("\n")
##                    self.sync()
            # The words always come in increments of some thousand, so if it's
            # not evenly divisible, you're probably at the end
            if len(finalstring) % 1000 != 0:
                done = True
                break
        # Save the image as the given filename in the Images folder
        try:
            self.b64_to_image(finalstring, "Images/" +
                              str(savepath))			# Decode the image
            self.displayPhotoPath = "Images/" + str(savepath)
            # Send the signal with the new image location to the main GUI
            self.mainWindow.newPicture.emit(self.displayPhotoPath)
        except:
            print("Error with filename, saved as newimage" + self.extension)
            self.mainWindow.stillNewText.emit(
                "Error with filename, saved as newimage" + self.extension)
            self.mainWindow.stillNewText.emit("Test633")
            sys.stdout.flush()
            # Save image as newimage.jpg due to a naming error in the Images
            # folder
            self.b64_to_image(finalstring, "Images/" +
                              "newimage" + self.extension)

        ### Clean Up ###
        #self.wordlength = 7000			# Reset the wordlength to the original
        print("Image Saved")
        self.mainWindow.stillNewText.emit("Image Saved")
        self.mainWindow.stillNewText.emit("Number of Packets Lost = " + str(failcount))
        sys.stdout.flush()

    def sync(self):
        """ Ensures both sender and receiver are at that the same point in their data streams """

        # Prepare to sync by resetting variables
        print("Attempting to Sync - This should take approx. 2 sec")
        self.mainWindow.stillNewText.emit(
            "Attempting to Sync - This should take approx. 2 sec")
        sync = b""
        addsync0 = b""
        addsync1 = b""
        addsync2 = b""
        addsync3 = b""

        # Program is held until no data is being sent (timeout) or until the
        # pattern 's' 'y' 'n' 'c' is found ###
        while sync != b"sync":
            addsync0 = self.rfdSer.read()
            #addsync0 = (addsync0)
            sync = b""
            if addsync0 == '':
                break
            sync = addsync3 + addsync2 + addsync1 + addsync0
            #print(sync)
            addsync3 = addsync2
            addsync2 = addsync1
            addsync1 = addsync0

        # Notifies sender that the receiving end is now synced
        self.rfdSer.write(b'S')
        print("System Match")
        self.mainWindow.stillNewText.emit("System Match")
        self.rfdSer.flushInput()			# Clear the buffers to be ready
        self.rfdSer.flushOutput()
        sys.stdout.flush()
        return

    def connectiontest(self, numping):
        """ Determines the ping time between the Pi and the computer """

        # Send the Pi A until the acknowledge is received, or too much time has
        # passed ###
        self.rfdSer.write(b'6!')
        termtime = time.time() + 20
        timeCheck = time.time() + 1
        while self.rfdSer.read() != b'A':
            if timeCheck < time.time():
                print("Waiting for Acknowledge")
                self.mainWindow.stillNewText.emit("Waiting for Acknowledge")
                timeCheck = time.time() + 1
            #self.rfdSer.write('6!')
            if termtime < time.time():  # If too much time passed, let the user know and return
                print("No Acknowledge Received, Connection Error")
                self.mainWindow.stillNewText.emit(
                    "No Acknowledge Received, Connection Error")
                sys.stdout.flush()
                return
        avg = 0

        # Using the specifified number of pings, give the Pi 10 seconds per
        # ping to respond correctly, and record the times ###
        self.rfdSer.write(b'~')
        temp = ""
        for x in range(1, numping):
            sendtime = time.time()
            receivetime = 0
            termtime = sendtime + 10
            # Loop until you get a P back, or too much time has passed
            while (temp != '~') & (time.time() < termtime):
                self.rfdSer.write(b'~')
                temp = self.rfdSer.read()
                receivetime = time.time()
                if receivetime == 0:  # If too much time has passed and no valid response, print the error, write D, and return
                    print("Connection Error, No return ping within 10 seconds")
                    self.mainWindow.stillNewText.emit(
                        "Connection Error, No return ping within 10 seconds")
                    self.rfdSer.write(b'D')
                    sys.stdout.flush()
                    return
            else:  # Otherwise reset the temp variable, and accumulate the avg
                temp = ""
                avg += receivetime - sendtime
                #print (avg/x)
        self.rfdSer.write(b'D')

        ### Determine and print the average response time ###
        avg = avg / numping
        print("Ping Response Time = " + str(avg)[0:4] + " seconds")
        self.mainWindow.stillNewText.emit(
            "Ping Response Time = " + str(avg)[0:4] + " seconds\n")
        sys.stdout.flush()			# Clear the buffer

        return

    def image_to_b64(self, path):
        """ Converts an image to a base64 encoded String (ASCII characters) """

        with open(path, "rb") as imageFile:
            return base64.b64encode(imageFile.read())

    def b64_to_image(self, data, savepath):
        """ Converts a base64 encoded string of ASCII characters back to an image, the save path dictates image format """
        fl = open(savepath, "wb")
        fl.write(base64.decodebytes(data.encode('ascii')))
        fl.close()

    def gen_checksum(self, data):
        """ Generates a 32 character hash up to 10000 char length String(for checksum). If string is too long I've notice length irregularities in checksum """
        return hashlib.md5(data).hexdigest()

    def setInterrupt(self, arg):
        self.interrupt = arg
Esempio n. 20
0
class FilesystemMonitor(QObject):
    """
    Class provides all functions needed to work with filesystem
    in scope of project
    """
    max_file_name_length = MAX_FILE_NAME_LEN - 5
    selective_sync_conflict_suffix = "selective sync conflict"

    started = pyqtSignal()
    stopped = pyqtSignal()
    process_offline = pyqtSignal(bool)

    def __init__(self,
                 root,
                 events_processing_delay,
                 copies_storage,
                 get_sync_dir_size,
                 conflict_file_suffix='',
                 tracker=None,
                 storage=None,
                 excluded_dirs=(),
                 parent=None,
                 max_relpath_len=3096,
                 db_file_created_cb=None):
        QObject.__init__(self, parent=parent)
        freeze_support()

        self._tracker = tracker

        self._root = root

        self._path_converter = PathConverter(self._root)
        self._storage = storage if storage else Storage(
            self._path_converter, db_file_created_cb)
        self._copies_storage = copies_storage
        self._copies_storage.delete_copy.connect(self.on_delete_copy)
        self.possibly_sync_folder_is_removed = \
            self._storage.possibly_sync_folder_is_removed
        self.db_or_disk_full = self._storage.db_or_disk_full
        self._get_sync_dir_size = get_sync_dir_size
        self._conflict_file_suffix = conflict_file_suffix

        self._rsync = Rsync

        _hide_files = HIDDEN_FILES
        _hide_dirs = HIDDEN_DIRS

        self._clean_recent_copies()

        self._actions = FsEventActions(
            self._root,
            events_processing_delay=events_processing_delay,
            path_converter=self._path_converter,
            storage=self._storage,
            copies_storage=self._copies_storage,
            rsync=self._rsync,
            tracker=self._tracker,
            parent=None,
            max_relpath_len=max_relpath_len,
        )

        self._watch = WatchdogHandler(root=FilePath(self._root).longpath,
                                      hidden_files=_hide_files,
                                      hidden_dirs=_hide_dirs)

        self._download_watch = WatchdogHandler(root=FilePath(
            self._root).longpath,
                                               hidden_files=_hide_files,
                                               hidden_dirs=_hide_dirs,
                                               patterns=['*.download'],
                                               is_special=True)

        self._observer = ObserverWrapper(self._storage,
                                         self._get_sync_dir_size,
                                         self._tracker,
                                         parent=None)
        self._observer.event_handled.connect(
            self._observer.on_event_is_handled_slot)
        self._actions.event_passed.connect(
            lambda ev: self._observer.event_handled.emit(ev, False))
        self._actions.event_suppressed.connect(
            lambda ev: self._observer.event_handled.emit(ev, True))

        # Add FS root for events tracking
        self._observer.schedule(self._watch, root)

        self._local_processor = LocalProcessor(self._root, self._storage,
                                               self._path_converter,
                                               self._tracker)
        self.event_is_arrived = self._local_processor.event_is_arrived
        self._quiet_processor = QuietProcessor(self._root, self._storage,
                                               self._path_converter,
                                               self.Exceptions)

        self._files_list = FilesList(self._storage, self._root)

        self._thread = QThread()
        self._thread.started.connect(self._on_thread_started)
        self._actions.moveToThread(self._thread)
        self._observer.moveToThread(self._thread)

        self._watch.event_is_arrived.connect(self._on_event_arrived)
        self._download_watch.event_is_arrived.connect(self._on_event_arrived)
        self._actions.event_passed.connect(self._local_processor.process)

        self._local_events_flag = False
        self._actions.event_passed.connect(self._set_local_events_flag)

        self.error_happens = self._actions.error_happens
        self.no_disk_space = self._actions.no_disk_space
        self.idle = self._actions.idle
        self.working = self._actions.working
        self.file_added_to_ignore = self._actions.file_added_to_ignore
        self.file_removed_from_ignore = self._actions.file_removed_from_ignore
        self.file_added_to_indexing = self._actions.file_added_to_indexing
        self.file_removed_from_indexing = self._actions.file_removed_from_indexing
        self.file_added = self._actions.file_added
        self.file_modified = self._actions.file_modified
        self.file_deleted = Signal(str)
        self._actions.file_deleted.connect(self.file_deleted)
        self._quiet_processor.file_deleted.connect(self.file_deleted)
        self._quiet_processor.file_modified.connect(self.file_modified)
        self.file_moved = self._quiet_processor.file_moved
        self._actions.file_moved.connect(lambda o, n: self.file_moved(o, n))
        self.access_denied = self._quiet_processor.access_denied

        self.file_list_changed = self._files_list.file_list_changed
        self.file_added.connect(self._files_list.on_file_added)
        self.file_deleted.connect(self._files_list.on_file_deleted)
        self.file_moved.connect(self._files_list.on_file_moved)
        self.file_modified.connect(self._files_list.on_file_modified)
        self.idle.connect(self._files_list.on_idle)

        self.process_offline.connect(self._observer.process_offline_changes)

        self.copy_added = Signal(str)
        self._actions.copy_added.connect(self.copy_added)

        self._actions.rename_file.connect(self._rename_file)

        self.special_file_event = Signal(
            str,  # path
            int,  # event type
            str)  # new path
        self._special_files = list()
        self._excluded_dirs = list(map(FilePath, excluded_dirs))

        self._online_processing_allowed = False
        self._online_modifies_processing_allowed = False

        self._paths_with_modify_quiet = set()

    def on_initial_sync_finished(self):
        logger.debug("on_initial_sync_finished")
        self._actions.on_initial_sync_finished()
        if not self._actions.get_fs_events_count() \
                and not self._observer.is_processing_offline:
            self.idle.emit()

    def _on_processed_offline_changes(self):
        logger.debug("_on_processed_offline_changes")
        if not self._actions.get_fs_events_count():
            self.idle.emit()

    def on_initial_sync_started(self):
        logger.debug("on_initial_sync_started")
        self._actions.on_initial_sync_started()
        self._online_processing_allowed = False
        self._online_modifies_processing_allowed = False

    def start_online_processing(self):
        logger.debug("start_online_processing")
        if not self._online_processing_allowed:
            logger.debug("start_online_processing, emit process_offline")
            self.process_offline.emit(self._online_modifies_processing_allowed)
        self._online_processing_allowed = True

    def start_online_modifies_processing(self):
        logger.debug("start_online_modifies_processing")
        if not self._online_modifies_processing_allowed:
            logger.debug(
                "start_online_modifies_processing, emit process_offline")
            self.process_offline.emit(True)
        self._online_modifies_processing_allowed = True

    def get_root(self):
        return self._root

    def root_exists(self):
        return op.isdir(self._root)

    def _on_thread_started(self):
        logger.info("Start monitoring of '%s'", self._root)
        self._observer.offline_event_occured.connect(self._on_event_arrived)
        self._observer.processed_offline_changes.connect(
            self._on_processed_offline_changes)
        self.started.emit()
        self._actions.start.emit()
        self._observer.start.emit()
        self._local_events_flag = False

    @benchmark
    def start(self):
        logger.debug("start")
        self._observer.set_active()
        if self._thread.isRunning():
            self._on_thread_started()
        else:
            self._thread.start()
        self._files_list.start()

    def stop(self):
        logger.info("stopped monitoring")
        try:
            self._observer.offline_event_occured.disconnect(
                self._on_event_arrived)
        except RuntimeError:
            logger.warning("Can't disconnect offline_event_occured")
        try:
            self._observer.processed_offline_changes.disconnect(
                self._on_processed_offline_changes)
        except RuntimeError:
            logger.warning("Can't disconnect processed_offline_changes")
        self._actions.stop()
        self._observer.stop()
        self._files_list.stop()
        self.stopped.emit()

    def quit(self):
        self.stop()
        self._thread.quit()
        self._thread.wait()

    def is_processing(self, file_path):
        return self._actions.is_processing(
            self._path_converter.create_abspath(file_path))

    def is_known(self, file_path):
        if file_path.endswith(FILE_LINK_SUFFIX):
            file_path = file_path[:-len(FILE_LINK_SUFFIX)]
        return self._storage.get_known_file(file_path) is not None

    def process_offline_changes(self):
        if self._local_events_flag:
            self.process_offline.emit(self._online_modifies_processing_allowed)
            self._local_events_flag = False

    def _set_local_events_flag(self, fs_event):
        if not fs_event.is_offline:
            self._local_events_flag = True

    def clean_storage(self):
        self._storage.clean()
        delete_file_links(self._root)

    def clean_copies(self, with_files=True):
        self._copies_storage.clean(with_files=with_files)

    def move_files_to_copies(self):
        with self._storage.create_session(read_only=False,
                                          locked=True) as session:
            files_with_hashes = session\
                .query(File.relative_path, File.file_hash) \
                .filter(File.is_folder == 0) \
                .all()
            copies_dir = get_copies_dir(self._root)
            for (file, hashsum) in files_with_hashes:
                hash_path = op.join(copies_dir, hashsum)
                file_path = self._path_converter.create_abspath(file)
                if not op.exists(hash_path):
                    try:
                        os.rename(file_path, hash_path)
                    except Exception as e:
                        logger.error("Error moving file to copy: %s", e)
                remove_file(file_path)
        abs_path = FilePath(self._root).longpath
        folders_plus_hidden = [
            self._path_converter.create_abspath(f)
            for f in os.listdir(abs_path) if f not in HIDDEN_DIRS
        ]
        for folder in folders_plus_hidden:
            if not op.isdir(folder):
                continue

            try:
                remove_dir(folder)
            except Exception as e:
                logger.error("Error removing dir '%s' (%s)", folder, e)
        logger.info("Removed all files and folders")
        self._storage.clean()

    def clean(self):
        files = self._storage.get_known_files()
        for file in files:
            try:
                remove_file(file)
            except Exception as e:
                logger.error("Error removing file '%s' (%s)", file, e)
        folders = self._storage.get_known_folders()
        for folder in sorted(folders, key=len):
            try:
                remove_dir(folder)
            except Exception as e:
                logger.error("Error removing dir '%s' (%s)", folder, e)
        logger.info("Removed all files and folders")
        self._storage.clean()

    def accept_delete(self,
                      path,
                      is_directory=False,
                      events_file_id=None,
                      is_offline=True):
        '''
        Processes file deletion

        @param path Name of file relative to sync directory [unicode]
        '''

        full_path = self._path_converter.create_abspath(path)
        object_type = 'directory' if is_directory else 'file'

        logger.debug("Deleting '%s' %s...", path, object_type)
        if is_directory:
            self._quiet_processor.delete_directory(full_path, events_file_id)
        else:
            self._quiet_processor.delete_file(full_path, events_file_id,
                                              is_offline)
        self.file_removed_from_indexing.emit(FilePath(full_path), True)

        logger.info("'%s' %s is deleted", path, object_type)

    def set_patch_uuid(self, patch_path, diff_file_uuid):
        shutil.move(patch_path, self.get_patch_path(diff_file_uuid))

    def get_patch_path(self, diff_file_uuid):
        return os.path.join(get_patches_dir(self._root), diff_file_uuid)

    def create_directory(self, path, events_file_id):
        full_path = self._path_converter.create_abspath(path)
        try:
            self._quiet_processor.create_directory(
                full_path,
                events_file_id=events_file_id,
                wrong_file_id=self.Exceptions.WrongFileId)
        except AssertionError:
            self._on_event_arrived(
                FsEvent(DELETE,
                        op.dirname(full_path),
                        True,
                        is_offline=True,
                        quiet=True))
            raise

    def apply_patch(self, filename, patch, new_hash, old_hash, events_file_id):
        '''
        Applies given patch for the file specified

        @param filename Name of file relative to sync directory [unicode]
        @param patch Patch data [dict]
        '''

        full_fn = self._path_converter.create_abspath(filename)

        try:
            self._apply_patch(full_fn,
                              patch,
                              new_hash,
                              old_hash,
                              events_file_id=events_file_id)
        except AssertionError:
            self._on_event_arrived(
                FsEvent(DELETE,
                        op.dirname(full_fn),
                        True,
                        is_offline=True,
                        quiet=True))
            raise

    def accept_move(self,
                    src,
                    dst,
                    is_directory=False,
                    events_file_id=None,
                    is_offline=True):
        src_full_path = self._path_converter.create_abspath(src)
        dst_full_path = self._path_converter.create_abspath(dst)

        try:
            object_type = 'directory' if is_directory else 'file'
            logger.debug("Moving '%s' %s to '%s'...", src, object_type, dst)
            if is_directory:
                self._quiet_processor.move_directory(
                    src_full_path,
                    dst_full_path,
                    events_file_id,
                    self.Exceptions.FileAlreadyExists,
                    self.Exceptions.FileNotFound,
                    wrong_file_id=self.Exceptions.WrongFileId)
            else:
                self._quiet_processor.move_file(
                    src_full_path,
                    dst_full_path,
                    events_file_id,
                    self.Exceptions.FileAlreadyExists,
                    self.Exceptions.FileNotFound,
                    wrong_file_id=self.Exceptions.WrongFileId,
                    is_offline=is_offline)
            logger.info("'%s' %s is moved to '%s'", src, object_type, dst)
            self.file_removed_from_indexing.emit(FilePath(src_full_path), True)
        except AssertionError:
            self._on_event_arrived(
                FsEvent(DELETE,
                        op.dirname(dst_full_path),
                        True,
                        is_offline=True,
                        quiet=True))
            raise

    def change_events_file_id(self, old_id, new_id):
        self._storage.change_events_file_id(old_id, new_id)

    class Exceptions(object):
        """ User-defined exceptions are stored here """
        class FileNotFound(Exception):
            """ File doesn't exist exception"""
            def __init__(self, file):
                self.file = file

            def __str__(self):
                return repr(self.file)

        class FileAlreadyExists(Exception):
            """ File already exists exception (for move) """
            def __init__(self, path):
                self.path = path

            def __str__(self):
                return "File already exists {}".format(self.path)

        class AccessDenied(Exception):
            """ Access denied exception (for move or delete) """
            def __init__(self, path):
                self.path = path

            def __str__(self):
                return "Access denied for {}".format(self.path)

        class WrongFileId(Exception):
            """ Wrong file if exception """
            def __init__(self, path, file_id_expected=None, file_id_got=None):
                self.path = path
                self.file_id_expected = file_id_expected
                self.file_id_got = file_id_got

            def __str__(self):
                return "Wrong file id for {}. Expected id {}. Got id {}".format(
                    self.path, self.file_id_expected, self.file_id_got)

        class CopyDoesNotExists(Exception):
            def __init__(self, hash):
                self.hash = hash

            def __str__(self):
                return "Copy with hash {} does not exists".format(self.hash)

    def _apply_patch(self,
                     filename,
                     patch,
                     new_hash,
                     old_hash,
                     silent=True,
                     events_file_id=None):
        start_time = time.time()
        patch_size = os.stat(patch).st_size
        success = False
        try:
            patched_new_hash, old_hash = self._quiet_processor.patch_file(
                filename,
                patch,
                silent=silent,
                events_file_id=events_file_id,
                wrong_file_id=self.Exceptions.WrongFileId)
            assert patched_new_hash == new_hash
            success = True
            self.copy_added.emit(new_hash)
        except Rsync.AlreadyPatched:
            success = True
        except:
            raise
        finally:
            if self._tracker:
                try:
                    file_size = os.stat(filename).st_size
                except OSError:
                    file_size = 0
                duration = time.time() - start_time
                self._tracker.monitor_patch_accept(file_size, patch_size,
                                                   duration, success)

    def generate_conflict_file_name(self,
                                    filename,
                                    is_folder=False,
                                    name_suffix=None,
                                    with_time=True):
        orig_filename = filename
        directory, filename = op.split(filename)
        original_ext = ''
        if is_folder:
            original_name = filename
        else:
            # consider ext as 2 '.'-delimited last filename substrings
            # if they don't contain spaces
            dots_list = filename.split('.')
            name_parts_len = len(dots_list)
            for k in range(1, min(name_parts_len, 3)):
                if ' ' in dots_list[-k]:
                    break

                original_ext = '.{}{}'.format(dots_list[k], original_ext)
                name_parts_len -= 1
            original_name = '.'.join(dots_list[:name_parts_len])

        index = 0
        if name_suffix is None:
            name_suffix = self._conflict_file_suffix
        date_today = date.today().strftime('%d-%m-%y') if with_time else ''
        suffix = '({} {})'.format(name_suffix, date_today)
        while len(bytes(suffix.encode('utf-8'))) > \
                int(self.max_file_name_length / 3):
            suffix = suffix[int(len(suffix) / 2):]

        name = '{}{}{}'.format(original_name, suffix, original_ext)
        while True:
            to_cut = len(bytes(name.encode('utf-8'))) - \
                     self.max_file_name_length
            if to_cut <= 0:
                break
            if len(original_name) > to_cut:
                original_name = original_name[:-to_cut]
            else:
                remained = to_cut - len(original_name) + 1
                original_name = original_name[:1]
                if remained < len(original_ext):
                    original_ext = original_ext[remained:]
                else:
                    original_ext = original_ext[int(len(original_ext) / 2):]
            name = '{}{}{}'.format(original_name, suffix, original_ext)

        while op.exists(
                self._path_converter.create_abspath(
                    FilePath(op.join(directory, name)))):
            index += 1
            name = '{}{} {}{}'.format(original_name, suffix, index,
                                      original_ext)
        conflict_file_name = FilePath(op.join(directory, name))
        logger.info(
            "Generated conflict file name: %s, original name: %s, "
            "is_folder: %s, name_suffix: %s, with_time: %s",
            conflict_file_name, orig_filename, is_folder, name_suffix,
            with_time)
        return conflict_file_name

    def move_file(self, src, dst, is_offline=True):
        src_full_path = self._path_converter.create_abspath(src)
        dst_full_path = self._path_converter.create_abspath(dst)
        is_offline = True if op.isdir(src_full_path) else is_offline
        src_hard_path = self._quiet_processor.get_hard_path(
            src_full_path, is_offline)
        dst_hard_path = self._quiet_processor.get_hard_path(
            dst_full_path, is_offline)

        if not op.exists(src_hard_path):
            raise self.Exceptions.FileNotFound(src_full_path)
        elif op.exists(dst_hard_path):
            raise self.Exceptions.FileAlreadyExists(dst_full_path)

        dst_parent_folder_path = op.dirname(dst_full_path)
        if not op.exists(dst_parent_folder_path):
            self._on_event_arrived(
                FsEvent(DELETE,
                        dst_parent_folder_path,
                        True,
                        is_offline=True,
                        quiet=True))

        try:
            os.rename(src_hard_path, dst_hard_path)
        except OSError as e:
            logger.warning("Can't move file (dir) %s. Reason: %s",
                           src_full_path, e)
            if e.errno == errno.EACCES:
                self._quiet_processor.access_denied()
                raise self.Exceptions.AccessDenied(src_full_path)
            else:
                raise e

    def copy_file(self, src, dst, is_directory=False, is_offline=True):
        is_offline = True if is_directory else is_offline
        src_full_path = self._path_converter.create_abspath(src)
        dst_full_path = self._path_converter.create_abspath(dst)
        src_hard_path = self._quiet_processor.get_hard_path(
            src_full_path, is_offline)
        dst_hard_path = self._quiet_processor.get_hard_path(
            dst_full_path, is_offline)

        if not op.exists(src_hard_path):
            raise self.Exceptions.FileNotFound(src_full_path)

        if is_directory:
            shutil.copytree(src_full_path, dst_full_path)
        else:
            common.utils.copy_file(src_hard_path, dst_hard_path)

    def restore_file_from_copy(self,
                               file_name,
                               copy_hash,
                               events_file_id,
                               search_by_id=False):
        try:
            old_hash = self._quiet_processor.create_file_from_copy(
                file_name,
                copy_hash,
                silent=True,
                events_file_id=events_file_id,
                search_by_id=search_by_id,
                wrong_file_id=self.Exceptions.WrongFileId,
                copy_does_not_exists=self.Exceptions.CopyDoesNotExists)
        except AssertionError:
            self._on_event_arrived(
                FsEvent(DELETE,
                        op.dirname(
                            self._path_converter.create_abspath(file_name)),
                        True,
                        is_offline=True,
                        quiet=True))
            raise

        return old_hash

    def create_file_from_copy(self,
                              file_name,
                              copy_hash,
                              events_file_id,
                              search_by_id=False):
        self.restore_file_from_copy(file_name,
                                    copy_hash,
                                    events_file_id=events_file_id,
                                    search_by_id=search_by_id)

    @benchmark
    def make_copy_from_existing_files(self, copy_hash):
        self._quiet_processor.make_copy_from_existing_files(copy_hash)

    def create_empty_file(self,
                          file_name,
                          file_hash,
                          events_file_id,
                          search_by_id=False,
                          is_offline=True):
        try:
            self._quiet_processor.create_empty_file(
                file_name,
                file_hash,
                silent=True,
                events_file_id=events_file_id,
                search_by_id=search_by_id,
                wrong_file_id=self.Exceptions.WrongFileId,
                is_offline=is_offline)
        except AssertionError:
            self._on_event_arrived(
                FsEvent(DELETE,
                        op.dirname(
                            self._path_converter.create_abspath(file_name)),
                        True,
                        is_offline=True,
                        quiet=True))
            raise

    def on_delete_copy(self, hash, with_signature=True):
        if not hash:
            logger.error("Invalid hash '%s'", hash)
            return
        copy = op.join(get_copies_dir(self._root), hash)
        try:
            remove_file(copy)
            logger.info("File copy deleted %s", copy)
            if not with_signature:
                return

            signature = op.join(get_signatures_dir(self._root), hash)
            remove_file(signature)
            logger.info("File copy signature deleted %s", signature)
        except Exception as e:
            logger.error(
                "Can't delete copy. "
                "Possibly sync folder is removed %s", e)
            self.possibly_sync_folder_is_removed()

    def delete_old_signatures(self, delete_all=False):
        logger.debug("Deleting old signatures...")
        self._quiet_processor.delete_old_signatures(
            get_signatures_dir(self._root), delete_all)

    def path_exists(self, path, is_offline=True):
        full_path = self._path_converter.create_abspath(path)
        hard_path = self._quiet_processor.get_hard_path(full_path, is_offline)
        return op.exists(hard_path)

    def rename_excluded(self, rel_path):
        logger.debug("Renaming excluded dir %s", rel_path)
        new_path = self.generate_conflict_file_name(
            rel_path,
            name_suffix=self.selective_sync_conflict_suffix,
            with_time=False)
        self.move_file(rel_path, new_path)

    def _rename_file(self, abs_path):
        rel_path = self._path_converter.create_relpath(abs_path)
        new_path = self.generate_conflict_file_name(rel_path,
                                                    is_folder=False,
                                                    name_suffix="",
                                                    with_time=True)
        self.move_file(rel_path, new_path)

    def db_file_exists(self):
        return self._storage.db_file_exists()

    def _clean_recent_copies(self):
        mask = op.join(get_copies_dir(self._root), "*.recent_copy_[0-9]*")
        recent_copies = glob.glob(mask)
        list(map(os.remove, recent_copies))

    def add_special_file(self, path):
        self._special_files.append(path)
        watch = None
        if not (path in FilePath(self._root)):
            watch = self._download_watch
        self._observer.add_special_file(path, watch)

    def remove_special_file(self, path):
        logger.debug("Removing special file %s...", path)
        if not (path in FilePath(self._root)):
            self._observer.remove_special_file(path)
        try:
            self._special_files.remove(path)
        except ValueError:
            logger.warning("Can't remove special file %s from list %s", path,
                           self._special_files)

    def change_special_file(self, old_file, new_file):
        self.add_special_file(new_file)
        self.remove_special_file(old_file)

    def _on_event_arrived(self, fs_event, is_special=False):
        logger.debug(
            "Event arrived %s, special %s, online_processing_allowed: %s, "
            "online_modifies_processing_allowed: %s", fs_event, is_special,
            self._online_processing_allowed,
            self._online_modifies_processing_allowed)
        if is_special or fs_event.src in self._special_files:
            self.special_file_event.emit(fs_event.src, fs_event.event_type,
                                         fs_event.dst)
        elif fs_event.is_offline or self._online_processing_allowed:
            if not self._online_modifies_processing_allowed and \
                    not fs_event.is_offline and fs_event.event_type == MODIFY:
                return
            elif fs_event.src in self._paths_with_modify_quiet \
                    and fs_event.event_type in (CREATE, MODIFY):
                fs_event.is_offline = True
                fs_event.quiet = True

            path = fs_event.src if fs_event.event_type == CREATE \
                else fs_event.dst if fs_event.event_type == MOVE else ""
            name = op.basename(path)
            parent_path = op.dirname(path)
            stripped_name = name.strip()
            if stripped_name != name:
                new_path = op.join(parent_path, stripped_name)
                if op.exists(new_path):
                    new_path = self.generate_conflict_file_name(
                        new_path,
                        is_folder=fs_event.is_dir,
                        name_suffix="",
                        with_time=True)
                logger.debug("Renaming '%s' to '%s'...", path, new_path)
                os.rename(FilePath(path).longpath, FilePath(new_path).longpath)

                path = new_path

                if fs_event.event_type == CREATE:
                    fs_event.src = new_path
                elif fs_event.event_type == MOVE:
                    fs_event.dst = new_path

            hidden_dir = FilePath(
                self._path_converter.create_abspath(HIDDEN_DIRS[0]))
            if fs_event.event_type == MOVE:
                if FilePath(fs_event.src) in hidden_dir or \
                        op.basename(fs_event.src).startswith('._'):
                    fs_event.event_type = CREATE
                    fs_event.src = fs_event.dst
                    fs_event.dst = None
                elif FilePath(fs_event.dst) in hidden_dir or \
                        op.basename(fs_event.dst).startswith('._'):
                    fs_event.event_type = DELETE
                    fs_event.dst = None
            if FilePath(fs_event.src) in hidden_dir or \
                    op.basename(fs_event.src).startswith('._'):
                return

            if FilePath(path) in self._excluded_dirs:
                self.rename_excluded(self._path_converter.create_relpath(path))
            else:
                self._actions.add_new_event(fs_event)

    def get_long_paths(self):
        return self._actions.get_long_paths()

    def set_excluded_dirs(self, excluded_dirs):
        self._excluded_dirs = list(map(FilePath, excluded_dirs))

    def remove_dir_from_excluded(self, directory):
        try:
            self._excluded_dirs.remove(directory)
        except Exception as e:
            logger.warning("Can't remove excluded dir %s from %s. Reason: %s",
                           directory, self._excluded_dirs, e)

    def sync_events_file_id(self, file_path, events_file_id, is_folder):
        self._quiet_processor.sync_events_file_id(file_path, events_file_id,
                                                  is_folder)

    def sync_events_file_id_by_old_id(self, events_file_id,
                                      old_events_file_id):
        self._quiet_processor.sync_events_file_id_by_old_id(
            events_file_id, old_events_file_id)

    def set_collaboration_folder_icon(self, folder_name):
        set_custom_folder_icon('collaboration', self._root, folder_name)

    def reset_collaboration_folder_icon(self, folder_name):
        reset_custom_folder_icon(self._root,
                                 folder_name,
                                 resource_name='collaboration')

    def reset_all_collaboration_folder_icons(self):
        root_folders = [
            f for f in os.listdir(self._root)
            if op.isdir(self._path_converter.create_abspath(f))
        ]
        logger.debug("root_folders %s", root_folders)
        list(map(self.reset_collaboration_folder_icon, root_folders))

    def get_excluded_dirs_to_change(self,
                                    excluded_dirs,
                                    src_path,
                                    dst_path=None):
        src_path = FilePath(src_path)
        if dst_path:
            dst_path = FilePath(dst_path)
        excluded_dirs = list(map(FilePath, excluded_dirs))
        dirs_to_add = []
        dirs_to_delete = list(filter(lambda ed: ed in src_path, excluded_dirs))
        if dst_path is not None and \
                not is_contained_in_dirs(dst_path, excluded_dirs):
            # we have to add new excluded dirs only if folder is not moved
            # to excluded dir
            l = len(src_path)
            dirs_to_add = [dst_path + d[l:] for d in dirs_to_delete]
        logger.debug(
            "get_excluded_dirs_to_change. "
            "excluded_dirs %s, src_path %s, dst_path %s, "
            "dirs_to_delete %s, dirs_to_add %s", excluded_dirs, src_path,
            dst_path, dirs_to_delete, dirs_to_add)
        return dirs_to_delete, dirs_to_add

    def change_excluded_dirs(self, dirs_to_delete, dirs_to_add):
        for directory in dirs_to_delete:
            self.remove_dir_from_excluded(directory)
        for directory in dirs_to_add:
            self._excluded_dirs.append(directory)

    def clear_excluded_dirs(self):
        self._excluded_dirs = []

    def get_fs_events_count(self):
        return self._actions.get_fs_events_count()

    def force_create_copies(self):
        self._storage.clear_files_hash_mtime()
        self.delete_old_signatures(delete_all=True)
        self._local_events_flag = True
        self.process_offline_changes()

    def get_file_list(self):
        return self._files_list.get()

    def get_actual_events_file_id(self, path, is_folder=None):
        abs_path = self._path_converter.create_abspath(path)
        file = self._storage.get_known_file(abs_path, is_folder=is_folder)
        return file.events_file_id if file else None

    def is_directory(self, path):
        abs_path = self._path_converter.create_abspath(path)
        return op.isdir(abs_path)

    def set_waiting(self, to_wait):
        self._actions.set_waiting(to_wait)

    def set_path_quiet(self, path):
        logger.debug("Setting path %s quiet...", path)
        self._paths_with_modify_quiet.add(FilePath(path))

    def clear_paths_quiet(self):
        logger.debug("Clearing quiet paths...")
        self._paths_with_modify_quiet.clear()

    def delete_files_with_empty_events_file_ids(self):
        if self._storage.delete_files_with_empty_events_file_ids():
            self.working.emit()

    def is_file_in_storage(self, events_file_id):
        return self._storage.get_known_file_by_id(events_file_id)
Esempio n. 21
0
class ObserverWrapper(QObject):
    """
    Wrapper for watchdog's Observer performing checks for offline events
    on observing start
    """

    event_handled = pyqtSignal(FsEvent, bool)

    start = pyqtSignal()

    processed_offline_changes = pyqtSignal()

    def __init__(self, storage, get_sync_dir_size, tracker=None, parent=None):
        """
        Constructor
        @param storage Storage class instance [Storage]
        @param tracker
            Statictics event tracker instance [stat_tracking.Tracker] or None
        """
        QObject.__init__(self, parent=parent)
        self._storage = storage
        self._observer = None
        self._active = True
        self._root_handlers = {}
        self._tracker = tracker
        self._reset_stats()
        self._started = False
        self.is_processing_offline = False
        self._processed_offline_changes = False
        self._get_sync_dir_size = get_sync_dir_size
        self._special_dirs = dict()
        self._special_files = set()
        self._lock = RLock()
        self.start.connect(self._start)

        # Signal to be emitted when detecting offline changes
        self.offline_event_occured = Signal(FsEvent, bool)

    def has_processed_offline_changes(self):
        return self._processed_offline_changes

    def _reset_stats(self):
        self._start_time = 0
        self._offline_stats_count = 0
        self._online_stats = defaultdict(int)
        self._offline_stats = defaultdict(int)
        self._start_stats_sended = False

    def set_active(self, active=True):
        logger.debug("set_active: %s", active)
        self._active = active

    def _start(self):
        """
        Starts monitoring of roots added with schedule().
        Creates wrapped observer instance
        """
        if not self._active:
            return
        logger.debug("Start")
        self._reset_stats()
        # Initialize observer
        self._observer = Observer()
        self._observer.start()
        # Detect offline changes (if any)
        self.is_processing_offline = False
        self._processed_offline_changes = False
        self._started = True

        self.process_offline_changes(process_modifies=False)
        # Register roots added previously
        with self._lock:
            if not self._active or not self._started:
                return
            for root, (event_handler,
                       recursive) in self._root_handlers.items():
                self._schedule_root(event_handler, root, recursive)

    def _schedule_root(self, event_handler, root, recursive):
        logger.info("Starting watching root '%s'...", root)
        if not self._observer:
            return
        return self._observer.schedule(event_handler,
                                       root,
                                       recursive=recursive)

    def stop(self):
        """
        Stops monitoring of roots added with schedule()
        """

        logger.debug("Stop")
        self._active = False
        if not self._started:
            logger.warning("Already stopped")
            return

        logger.info("Stop watching all roots")
        self._started = False
        self._processed_offline_changes = False
        try:
            self._observer.unschedule_all()
            self._observer.stop()
            self._observer.join()
        except Exception as e:
            logger.error('Exception while stopping fs observer: %s', e)
        self._observer = None

    def schedule(self, event_handler, root, recursive=True):
        """
        Register given event handler to be used for events from given root path

        @param event_handler
            Observer class instance [watchdog.observers.BaseObserver]
        @param root Path (absolute) to process event from [unicode]
        @param recursive
            Flag enabling processing events from nested folders/files [bool]
        """

        root = FilePath(root).longpath
        self._root_handlers[root] = (event_handler, recursive)
        watch = None
        if self._started:
            watch = self._schedule_root(event_handler, root, recursive)
        return watch

    def process_offline_changes(self, process_modifies):
        logger.debug("process_offline_changes")
        if self.is_processing_offline or not self._started:
            logger.debug(
                "process_offline_changes, already processing offline changes")
            return

        self.is_processing_offline = True
        for root in self._root_handlers.copy():
            self._check_root(root, process_modifies)

    def _check_root(self, root, process_modifies):
        """
        Check given root path for offline events

        @param root Path (absolute) to be checked [unicode]
        """
        if FilePath(root) in self._special_dirs:
            return

        logger.info("Checking root '%s' folder for offline changes...", root)

        self._start_time = time.time()

        logger.debug("Obtaining known files from storage...")
        known_files = set(self._storage.get_known_files())
        if not self._active or not self._started:
            return
        logger.debug("Known files: %s", len(known_files))

        logger.debug("Obtaining actual files and folders from filesystem...")
        actual_folders, actual_files = get_files_dir_list(
            root,
            exclude_dirs=self._root_handlers[root][0].hidden_dirs,
            exclude_files=self._root_handlers[root][0].hidden_files)
        if not self._active or not self._started:
            return
        logger.debug("Actual folders: %s", len(actual_folders))
        logger.debug("Actual files: %s", len(actual_files))

        actual_files = set(map(FilePath, actual_files)) - self._special_files
        actual_folders = set(map(FilePath, actual_folders))

        if not self._active or not self._started:
            return

        self._offline_stats['file_COUNT'] = len(actual_files)

        logger.debug("Finding files that were created...")
        files_created = actual_files.difference(known_files)
        if not self._active or not self._started:
            return

        logger.debug("Finding files that were deleted...")
        files_deleted = known_files.difference(actual_files)
        if not self._active or not self._started:
            return

        for path in files_deleted.copy():
            if not self._active or not self._started:
                return

            path_plus_suffix = path + FILE_LINK_SUFFIX
            if path_plus_suffix in files_created:
                files_deleted.discard(path)
                files_created.discard(path_plus_suffix)

        logger.debug("Obtaining known folders from storage...")
        known_folders = set(self._storage.get_known_folders())

        if not self._active or not self._started:
            return
        logger.debug("Known folders: %s", len(known_folders))

        logger.debug("Finding folders that were created...")
        folders_created = sorted(actual_folders.difference(known_folders),
                                 key=len,
                                 reverse=True)
        if not self._active or not self._started:
            return

        logger.debug("Finding folders that were deleted...")
        folders_deleted = sorted(known_folders.difference(actual_folders),
                                 key=len,
                                 reverse=True)

        if not self._active or not self._started:
            return

        logger.info("Folders found: %s (created: %s, deleted: %s)",
                    len(actual_folders), len(folders_created),
                    len(folders_deleted))

        self._offline_stats['dir_COUNT'] = len(actual_folders)

        logger.debug("Appending deleted files to processing...")
        for filename in files_deleted:
            if not self._active or not self._started:
                return
            self._emit_offline_event(
                FsEvent(event_type=DELETE,
                        src=filename,
                        is_dir=False,
                        is_offline=True))

        logger.debug("Appending deleted folders to processing...")
        for foldername in folders_deleted:
            if not self._active or not self._started:
                return
            self._emit_offline_event(
                FsEvent(event_type=DELETE,
                        src=foldername,
                        is_dir=True,
                        is_offline=True))

        logger.debug("Appending created files to processing...")
        for filename in files_created:
            if not self._active or not self._started:
                return
            self._emit_offline_event(
                FsEvent(event_type=CREATE,
                        src=filename,
                        is_dir=False,
                        is_offline=True))

        logger.debug("Appending created folders to processing...")
        for foldername in folders_created:
            if not self._active or not self._started:
                return
            self._emit_offline_event(
                FsEvent(event_type=CREATE,
                        src=foldername,
                        is_dir=True,
                        is_offline=True))

        self._processed_offline_changes = True
        self.is_processing_offline = False
        logger.debug("Emitting ofline events processed signal")
        self.processed_offline_changes.emit()

        if not process_modifies:
            return

        logger.debug("Finding files with possible modifications...")
        same_files = actual_files.intersection(known_files)

        if not self._active or not self._started:
            return

        logger.info(
            "Files found: %s (created: %s, deleted: %s, remaining: %s)",
            len(actual_files), len(files_created), len(files_deleted),
            len(same_files))

        logger.debug("Appending possible modified files to processing...")
        for filename in same_files:
            if not self._active or not self._started:
                return
            # Actual file modification will be checked by event filters
            # applied in WatchdogHandler instance
            self._emit_offline_event(
                FsEvent(
                    event_type=MODIFY,
                    src=filename,
                    is_dir=False,
                    is_offline=True,
                    quiet=True,
                ))
        logger.debug("work complete")

    def _emit_offline_event(self, fs_event):
        assert fs_event.is_offline
        self._offline_stats_count += 1
        self.offline_event_occured.emit(fs_event, False)

    def on_event_is_handled_slot(self, fs_event, suppressed=False):
        """
        Slot to process FsEventFilters.event_is_handled signal

        @param fs_event Event being reported [FsEvent]
        """

        logger.info("on_event_is_handled_slot: %s", fs_event)

        assert len(self._root_handlers) > 0

        if fs_event.is_offline:
            stat = self._offline_stats
        else:
            stat = self._online_stats

        # Determine name of stat counter
        stat_name_prefix = 'dir_' if fs_event.is_dir else 'file_'
        event_name = event_names[fs_event.event_type]
        stat_name = stat_name_prefix + event_name

        if suppressed:
            if not fs_event.is_dir:
                stat['file_IGNORED'] += 1
        else:
            # Increment counter corresponding to event obtained
            stat[stat_name] += 1

        if fs_event.is_offline:
            # Not handled offline events remaining
            if self._offline_stats_count > 0:
                self._offline_stats_count -= 1
                # All emitted events has been handled
                if self._offline_stats_count == 0:
                    # Online total counts should be based on offline ones
                    self._online_stats['file_COUNT'] += \
                        self._offline_stats['file_COUNT']
                    self._online_stats['dir_COUNT'] += \
                        self._offline_stats['dir_COUNT']
                    # Send stats accumulated
                    self._send_start_stats()
            else:
                logger.warning(
                    "FsEventFilters handled more offline events than "
                    "have been emitted by ObserverWrapper")
        else:
            counter_name = stat_name_prefix + 'COUNT'
            if event_name == 'CREATE':
                self._online_stats[counter_name] += 1
            elif event_name == 'DELETE':
                self._online_stats[counter_name] -= 1

    def _send_start_stats(self):
        if self._start_stats_sended:
            return

        duration = time.time() - self._start_time
        logger.info("ObserverWrapper started in %s seconds", duration)

        if self._tracker:
            self._tracker.monitor_start(self._offline_stats['file_COUNT'],
                                        self._offline_stats['dir_COUNT'],
                                        self._offline_stats['file_CREATE'],
                                        self._offline_stats['file_MODIFY'],
                                        self._offline_stats['file_MOVE'],
                                        self._offline_stats['file_DELETE'],
                                        self._offline_stats['dir_CREATE'],
                                        self._offline_stats['dir_DELETE'],
                                        self._get_sync_dir_size(), duration)
        self._start_stats_sended = True

    def _send_stop_stats(self):
        duration = time.time() - self._start_time
        logger.info("ObserverWrapper worked %s seconds", duration)

        if self._tracker:
            self._tracker.monitor_stop(self._online_stats['file_COUNT'],
                                       self._online_stats['dir_COUNT'],
                                       self._online_stats['file_CREATE'],
                                       self._online_stats['file_MODIFY'],
                                       self._online_stats['file_MOVE'],
                                       self._online_stats['file_DELETE'],
                                       self._online_stats['dir_CREATE'],
                                       self._online_stats['dir_MOVE'],
                                       self._online_stats['dir_DELETE'],
                                       duration,
                                       self._online_stats['file_IGNORED'])

    def add_special_file(self, path, event_handler):
        # Observer has to be started here. So watch is not None
        with self._lock:
            if not event_handler:
                self._special_files.add(FilePath(path))
            else:
                special_dir = FilePath(op.dirname(path))
                watch = self.schedule(event_handler,
                                      special_dir,
                                      recursive=False)
                self._special_dirs[special_dir] = watch

    def remove_special_file(self, path):
        logger.debug("Removing special file %s...", path)
        with self._lock:
            special_dir = FilePath(op.dirname(path))
            if special_dir in self._special_dirs:
                watch = self._special_dirs.pop(special_dir)
                self._root_handlers.pop(special_dir.longpath)
                if self._started:
                    self._observer.unschedule(watch)
                logger.debug("Unscheduled path %s", path)
            elif FilePath(path) in self._special_files:
                self._special_files.discard(FilePath(path))
            else:
                logger.warning("Can't remove special file %s from %s and %s",
                               path, self._special_dirs, self._special_files)