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()
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))
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))
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))
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() # 然后窗口关闭
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)
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
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
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
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)
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
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))
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())
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))
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
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
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()
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)
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
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)
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)