def initialize_buckets_table(self): self.storj_engine = StorjEngine() # init StorjEngine logger.info("resolving buckets") model = QStandardItemModel(1, 1) # initialize model for inserting to table model.setHorizontalHeaderLabels(['Name', 'Storage', 'Transfer', 'ID']) i = 0 try: for bucket in self.storj_engine.storj_client.bucket_list(): item = QStandardItem(bucket.name) model.setItem(i, 0, item) # row, column, item (QStandardItem) item = QStandardItem(str(bucket.storage)) model.setItem(i, 1, item) # row, column, item (QStandardItem) item = QStandardItem(str(bucket.transfer)) model.setItem(i, 2, item) # row, column, item (QStandardItem) item = QStandardItem(bucket.id) model.setItem(i, 3, item) # row, column, item (QStandardItem) i = i + 1 except sjexc.StorjBridgeApiError as e: QtGui.QMessageBox.about( self, 'Unhandled bucket resolving exception', 'Exception: %s' % e) self.bucket_manager_ui.total_buckets_label.setText(str(i)) # set label of user buckets number self.bucket_manager_ui.bucket_list_tableview.setModel(model) self.bucket_manager_ui.bucket_list_tableview.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
def __init__(self, parent=None, bucketid=None, fileid=None): QtGui.QWidget.__init__(self, parent) self.file_mirrors_list_ui = Ui_FileMirrorsList() self.file_mirrors_list_ui.setupUi(self) # model = self.file_mirrors_list_ui.established_mirrors_tree.model() self.file_mirrors_list_ui.mirror_details_bt.clicked.connect( lambda: self.open_mirror_details_window("established")) self.file_mirrors_list_ui.mirror_details_bt_2.clicked.connect( lambda: self.open_mirror_details_window("available")) self.file_mirrors_list_ui.quit_bt.clicked.connect(self.close) self.connect(self, QtCore.SIGNAL("showStorjBridgeException"), self.show_storj_bridge_exception) self.connect(self, QtCore.SIGNAL("showUnhandledException"), self.show_unhandled_exception) # self.connect(self.file_mirrors_list_ui.established_mirrors_tree, QtCore.SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.open_mirror_details_window) # self.connect(self.file_mirrors_list_ui.established_mirrors_tree, QtCore.SIGNAL('selectionChanged()'), self.open_mirror_details_window) # QtCore.QObject.connect(self.file_mirrors_list_ui.established_mirrors_tree.selectionModel(), QtCore.SIGNAL('selectionChanged(QItemSelection, QItemSelection)'), # self.open_mirror_details_window) # self.file_mirrors_list_ui.established_mirrors_tree. self.bucketid = bucketid self.fileid = fileid self.file_mirrors_list_ui.file_id_label.setText(html_format_begin + str(self.fileid) + html_format_end) logger.info(self.fileid) self.storj_engine = StorjEngine() # init StorjEngine self.createNewMirrorListInitializationThread()
def refresh_overall_progress(self, base_percent): total_percent_to_upload = self.all_shards_count * 100 total_percent_uploaded = sum(self.shard_upload_percent_list) * 100 actual_percent_uploaded = total_percent_uploaded / total_percent_to_upload total_percent = (base_percent * 100) + (0.90 * actual_percent_uploaded) logger.info(str(actual_percent_uploaded) + str(base_percent) + "total_percent_uploaded") # actual_upload_progressbar_value = self.ui_single_file_upload.overall_progress.value() self.ui_single_file_upload.overall_progress.setValue(int(total_percent))
def add_row_upload_queue_table(self, row_data): self.upload_queue_progressbar_list.append(QProgressBar()) self.upload_queue_table_row_count = self.ui_single_file_upload.shard_queue_table_widget.rowCount() self.ui_single_file_upload.shard_queue_table_widget.setRowCount(self.upload_queue_table_row_count + 1) self.ui_single_file_upload.shard_queue_table_widget.setCellWidget(self.upload_queue_table_row_count, 0, self.upload_queue_progressbar_list[self.upload_queue_table_row_count]) self.ui_single_file_upload.shard_queue_table_widget.setItem(self.upload_queue_table_row_count, 1, QtGui.QTableWidgetItem(row_data["hash"])) self.ui_single_file_upload.shard_queue_table_widget.setItem(self.upload_queue_table_row_count, 2, QtGui.QTableWidgetItem(str(row_data["farmer_address"]) + ":" + str(row_data["farmer_port"]))) self.ui_single_file_upload.shard_queue_table_widget.setItem(self.upload_queue_table_row_count, 3, QtGui.QTableWidgetItem(str(row_data["state"]))) self.ui_single_file_upload.shard_queue_table_widget.setItem(self.upload_queue_table_row_count, 4, QtGui.QTableWidgetItem(str(row_data["token"]))) self.ui_single_file_upload.shard_queue_table_widget.setItem(self.upload_queue_table_row_count, 5, QtGui.QTableWidgetItem(str(row_data["shard_index"]))) self.upload_queue_progressbar_list[self.upload_queue_table_row_count].setValue(0) logger.info(row_data)
def open_mirrors_list_window(self): self.current_bucket_index = self.file_manager_ui.bucket_select_combo_box.currentIndex() self.current_selected_bucket_id = self.bucket_id_list[self.current_bucket_index] tablemodel = self.file_manager_ui.files_list_tableview.model() rows = sorted(set(index.row() for index in self.file_manager_ui.files_list_tableview.selectedIndexes())) i = 0 for row in rows: logger.info('Row %d is selected' % row) index = tablemodel.index(row, 3) # get file ID # We suppose data are strings selected_file_id = str(tablemodel.data(index).toString()) self.file_mirrors_list_window = FileMirrorsListUI(self, str(self.current_selected_bucket_id), selected_file_id) self.file_mirrors_list_window.show() i += 1 if i == 0: QtGui.QMessageBox.about(self, "Warning!", "Please select file from file list!") logger.debug(1)
def update_files_list(self): self.tools = Tools() model = QtGui.QStandardItemModel(1, 1) # initialize model for inserting to table model.setHorizontalHeaderLabels(['File name', 'File size', 'Mimetype', 'File ID']) self.current_bucket_index = self.file_manager_ui.bucket_select_combo_box.currentIndex() self.current_selected_bucket_id = self.bucket_id_list[self.current_bucket_index] i = 0 try: for self.file_details in self.storj_engine.storj_client.bucket_files(str(self.current_selected_bucket_id)): item = QtGui.QStandardItem(str(self.file_details["filename"])) model.setItem(i, 0, item) # row, column, item (StandardItem) file_size_str = self.tools.human_size(int(self.file_details["size"])) # get human readable file size item = QtGui.QStandardItem(str(file_size_str)) model.setItem(i, 1, item) # row, column, item (QQtGui.StandardItem) item = QtGui.QStandardItem(str(self.file_details["mimetype"])) model.setItem(i, 2, item) # row, column, item (QStandardItem) item = QtGui.QStandardItem(str(self.file_details["id"])) model.setItem(i, 3, item) # row, column, item (QStandardItem) i = i + 1 logger.info(self.file_details) except sjexc.StorjBridgeApiError as e: logger.error(e) self.file_manager_ui.files_list_tableview.clearFocus() self.file_manager_ui.files_list_tableview.setModel(model) self.file_manager_ui.files_list_tableview.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
def login(self): # take login action self.email = str(self.login_ui.email.text()).strip() # get mail self.password = str( self.login_ui.password.text()).strip() # get password self.storj_client = storj.Client(email=self.email, password=self.password) success = False # take login action - check credentials by listing keys :D try: self.storj_client.key_list() success = True except storj.exception.StorjBridgeApiError as e: j = json.loads(str(e)) if j.get("error") == "Invalid email or password": QtGui.QMessageBox.about( self, "Warning", "Invalid email or password - access denied. " "Please check your credentials and try again!") else: QtGui.QMessageBox.about(self, "Unhandled exception", "Exception: " + str(e)) if success: self.account_manager = AccountManager( self.email, self.password) # init account manager self.account_manager.save_account_credentials( ) # save login credentials and state msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Information, "Success", "Successfully loged in!", QtGui.QMessageBox.Ok) result = msgBox.exec_() if result == QtGui.QMessageBox.Ok: logger.info("User {} succesfully logged in".format(self.email)) self.main_ui_window = MainUI(self) self.main_ui_window.show() self.close()
def initialize_mirrors_tree(self): # create model # model = QtGui.QFileSystemModel() # model.setRootPath(QtCore.QDir.currentPath()) self.file_mirrors_list_ui.loading_label_mirrors_established.setStyleSheet('color: red') # set loading color self.file_mirrors_list_ui.loading_label_mirrors_available.setStyleSheet('color: red') # set loading color self.mirror_tree_view_header = ['Shard Hash / Address', 'User agent', 'Last seed', 'Node ID'] ######################### set the model for established mirrors ################################## self.established_mirrors_model = QtGui.QStandardItemModel() self.established_mirrors_model.setHorizontalHeaderLabels(self.mirror_tree_view_header) self.established_mirrors_tree_view = self.file_mirrors_list_ui.established_mirrors_tree self.established_mirrors_tree_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) self.established_mirrors_tree_view.setModel(self.established_mirrors_model) self.established_mirrors_tree_view.setUniformRowHeights(True) self.file_mirrors_list_ui.available_mirrors_tree.setModel(self.established_mirrors_model) divider = 0 group = 1 self.established_mirrors_count_for_file = 0 recent_shard_hash = "" parent1 = QtGui.QStandardItem('') try: for file_mirror in self.storj_engine.storj_client.file_mirrors(str(self.bucketid), str(self.fileid)): for mirror in file_mirror.established: self.established_mirrors_count_for_file += 1 logger.info(file_mirror.established) if mirror["shardHash"] != recent_shard_hash: parent1 = QtGui.QStandardItem('Shard with hash {}'.format(mirror["shardHash"])) divider = divider + 1 self.established_mirrors_model.appendRow(parent1) child1 = QtGui.QStandardItem(str(mirror["contact"]["address"] + ":" + str(mirror["contact"]["port"]))) child2 = QtGui.QStandardItem(str(mirror["contact"]["userAgent"])) child3 = QtGui.QStandardItem(str(mirror["contact"]["lastSeen"])) child4 = QtGui.QStandardItem(str(mirror["contact"]["nodeID"])) parent1.appendRow([child1, child2, child3, child4]) # span container columns # self.established_mirrors_tree_view.setFirstColumnSpanned(1, self.established_mirrors_tree_view.rootIndex(), True) recent_shard_hash = mirror["shardHash"] self.file_mirrors_list_ui.loading_label_mirrors_established.setText("") # dbQueryModel.itemData(treeView.selectedIndexes()[0]) ################################### set the model for available mirrors ######################################### self.available_mirrors_model = QtGui.QStandardItemModel() self.available_mirrors_model.setHorizontalHeaderLabels(self.mirror_tree_view_header) self.available_mirrors_tree_view = self.file_mirrors_list_ui.available_mirrors_tree self.available_mirrors_tree_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) self.available_mirrors_tree_view.setModel(self.available_mirrors_model) self.available_mirrors_tree_view.setUniformRowHeights(True) self.file_mirrors_list_ui.available_mirrors_tree.setModel(self.available_mirrors_model) divider = 0 self.available_mirrors_count_for_file = 0 recent_shard_hash_2 = "" parent2 = QtGui.QStandardItem('') for file_mirror in self.storj_engine.storj_client.file_mirrors(str(self.bucketid), str(self.fileid)): for mirror_2 in file_mirror.available: self.available_mirrors_count_for_file += 1 if mirror_2["shardHash"] != recent_shard_hash_2: parent2 = QtGui.QStandardItem('Shard with hash {}'.format(mirror_2["shardHash"])) divider = divider + 1 self.available_mirrors_model.appendRow(parent2) child1 = QtGui.QStandardItem(str(mirror_2["contact"]["address"] + ":" + str(mirror_2["contact"]["port"]))) child2 = QtGui.QStandardItem(str(mirror_2["contact"]["userAgent"])) child3 = QtGui.QStandardItem(str(mirror_2["contact"]["lastSeen"])) child4 = QtGui.QStandardItem(str(mirror_2["contact"]["nodeID"])) parent2.appendRow([child1, child2, child3, child4]) # span container columns # self.established_mirrors_tree_view.setFirstColumnSpanned(1, self.established_mirrors_tree_view.rootIndex(), True) recent_shard_hash_2 = mirror_2["shardHash"] self.file_mirrors_list_ui.loading_label_mirrors_available.setText("") self.file_mirrors_list_ui.established_mirrors_count.setText( html_format_begin + str(self.established_mirrors_count_for_file) + html_format_end) self.file_mirrors_list_ui.available_mirrors_count.setText( html_format_begin + str(self.available_mirrors_count_for_file) + html_format_end) except sjexc.StorjBridgeApiError as e: self.emit(QtCore.SIGNAL("showStorjBridgeException"), str(e)) # emit Storj Bridge Exception except Exception as e: self.emit(QtCore.SIGNAL("showUnhandledException"), str(e)) # emit unhandled Exception logger.error(e)
def initialize_node_details(self): try: self.node_details_content = self.storj_engine.storj_client.contact_lookup(str(self.nodeid)) self.node_details_ui.address_label.setText( str(self.node_details_content.address) ) # get given node address self.node_details_ui.last_timeout_label.setText( str(self.node_details_content.lastTimeout).replace('Z', '').replace('T', ' ') ) # get last timeout self.node_details_ui.timeout_rate_label.setText( str(self.node_details_content.timeoutRate)) # get timeout rate self.node_details_ui.user_agent_label.setText( str(self.node_details_content.userAgent) ) # get user agent self.node_details_ui.protocol_version_label.setText( str(self.node_details_content.protocol) ) # get protocol version self.node_details_ui.response_time_label.setText( str( self.node_details_content.responseTime)) # get farmer node response time self.node_details_ui.port_label.setText( str(self.node_details_content.port) ) # get farmer node port self.node_details_ui.node_id_label.setText( str(self.nodeid) ) # get farmer node response time self.node_details_ui.last_seen_label.setText( str(self.node_details_content.lastSeen).replace('Z', '').replace('T', ' ')) # get last seen # ping_to_node = self.tools.measure_ping_latency(str(self.node_details_content.address)) ip_addr = socket.gethostbyname(str(self.node_details_content.address)) obj = IPWhois(ip_addr) res = obj.lookup_whois() country = res["nets"][0]['country'] country_parsed = pycountry.countries.get(alpha_2=str(country)) country_full_name = country_parsed.name self.node_details_ui.country_label.setText( str(country_full_name)) # set full country name ### Display country flag ### self.scene = QtGui.QGraphicsScene() # scene.setSceneRect(-600,-600, 600,600) # self.scene.setSceneRect(-600, -600, 1200, 1200) # pic = QtGui.QPixmap("PL.png") # self.scene.addItem(QtGui.QGraphicsPixmapItem(pic)) # self.view = self.node_details_ui.country_graphicsView # self.view.setScene(self.scene) # self.view.setRenderHint(QtGui.QPainter.Antialiasing) # self.view.show() grview = self.node_details_ui.country_graphicsView() scene = QtGui.QGraphicsScene() scene.addPixmap(QtGui.QPixmap('PL.png')) grview.setScene(scene) grview.show() logger.info(country_full_name) except sjexc.StorjBridgeApiError as e: self.emit(QtCore.SIGNAL("showBridgeExceptionMessageBox"), str(e)) # emit signal to show message box with bridge exception except Exception as e: self.emit(QtCore.SIGNAL("showUnhandledExceptionMessageBox"), str(e)) # emit signal to show message box with unhandled exception
def upload_shard(self, shard, chapters, frame, file_name_ready_to_shard_upload): self.uploadblocksize = 4096 def read_in_chunks(file_object, shard_size, rowposition, blocksize=self.uploadblocksize, chunks=-1, shard_index=None): """Lazy function (generator) to read a file piece by piece. Default chunk size: 1k.""" i = 0 while chunks: data = file_object.read(blocksize) if not data: break yield data i += 1 t1 = float(shard_size) / float((self.uploadblocksize)) if shard_size <= (self.uploadblocksize): t1 = 1 percent_uploaded = int(round((100.0 * i) / t1)) logger.debug(i) chunks -= 1 self.emit(SIGNAL("updateShardUploadProgress"), int(rowposition), percent_uploaded) # update progress bar in upload queue table self.shard_upload_percent_list[shard_index] = percent_uploaded self.emit(SIGNAL("refreshOverallProgress"), 0.1) # update overall progress bar it = 0 contract_negotiation_tries = 0 while self.max_retries_negotiate_contract > contract_negotiation_tries: contract_negotiation_tries += 1 # emit signal to add row to upload queue table # self.emit(SIGNAL("addRowToUploadQueueTable"), "important", "information") self.ui_single_file_upload.current_state.setText( html_format_begin + "Adding shard " + str( chapters) + " to file frame and getting contract..." + html_format_end) # logger.warning('"log_event_type": "debug"') logger.warning('"title": "Negotiating contract"') logger.warning('"description": "Trying to negotiate storage \ contract for shard at inxed " + str(chapters) + "..."') # logger.warning(str({"log_event_type": "debug", "title": "Negotiating contract", # "description": "Trying to negotiate storage contract for shard at inxed " + str(chapters) + "..."})) try: frame_content = self.storj_engine.storj_client.frame_add_shard(shard, frame.id) # Add items to shard queue table view tablerowdata = {} tablerowdata["farmer_address"] = frame_content["farmer"]["address"] tablerowdata["farmer_port"] = frame_content["farmer"]["port"] tablerowdata["hash"] = str(shard.hash) tablerowdata["state"] = "Uploading..." tablerowdata["token"] = frame_content["token"] tablerowdata["shard_index"] = str(chapters) # logger.warning('"log_event_type": "debug"') logger.warning('"title": "Contract negotiated"') logger.warning('"description": "Storage contract negotiated with: "' + str(frame_content["farmer"]["address"]) + ":" + str(frame_content["farmer"]["port"])) # logger.warning(str({"log_event_type": "debug", "title": "Contract negotiated", # "description": "Storage contract negotiated with: " + str(frame_content["farmer"]["address"] + ":" + str(frame_content["farmer"]["port"]))})) self.emit(SIGNAL("addRowToUploadQueueTable"), tablerowdata) # add row to table rowcount = self.ui_single_file_upload.shard_queue_table_widget.rowCount() logger.debug(frame_content) logger.debug(shard) logger.debug(frame_content["farmer"]["address"]) farmerNodeID = frame_content["farmer"]["nodeID"] url = "http://" + frame_content["farmer"]["address"] + ":" +\ str(frame_content["farmer"]["port"]) + "/shards/" +\ frame_content["hash"] + "?token=" +\ frame_content["token"] logger.debug(url) # files = {'file': open(file_path + '.part%s' % chapters)} # headers = {'content-type: application/octet-stream', 'x-storj-node-id: ' + str(farmerNodeID)} self.set_current_status("Uploading shard " + str(chapters + 1) + " to farmer...") # begin recording exchange report exchange_report = storj.model.ExchangeReport() current_timestamp = int(time.time()) exchange_report.exchangeStart = str(current_timestamp) exchange_report.farmerId = str(farmerNodeID) exchange_report.dataHash = str(shard.hash) shard_size = int(shard.size) rowposition = rowcount farmer_tries = 0 response = None while self.max_retries_upload_to_same_farmer > farmer_tries: farmer_tries += 1 try: logger.warning(str({"log_event_type": "debug", "title": "Uploading shard", "description": "Uploading shard at index " + str(shard.index) + " to " + str( frame_content["farmer"]["address"] + ":" + str(frame_content["farmer"][ "port"]))})) with open(self.parametrs.tmpPath + file_name_ready_to_shard_upload + '-' + str(chapters + 1), 'rb') as f: response = requests.post(url, data=read_in_chunks(f, shard_size, rowposition, shard_index=chapters), timeout=1) j = json.loads(str(response.content)) if (j["result"] == "The supplied token is not accepted"): raise storj.exception.StorjFarmerError( storj.exception.StorjFarmerError.SUPPLIED_TOKEN_NOT_ACCEPTED) except storj.exception.StorjFarmerError as e: # upload failed due to Farmer Failure logger.error(e) if str(e) == str(storj.exception.StorjFarmerError.SUPPLIED_TOKEN_NOT_ACCEPTED): logger.error("The supplied token not accepted") # print "Exception raised while trying to negitiate contract: " + str(e) continue except Exception as e: self.emit(SIGNAL("updateUploadTaskState"), rowposition, "First try failed. Retrying... (" + str(farmer_tries) + ")") # update shard upload state # logger.warning('"log_event_type": "warning"') logger.warning('"title": "Shard upload error"') logger.warning('"description": "Error while uploading \ shard to: "' + frame_content["farmer"]["address"] + ":" + str(frame_content["farmer"]["port"]) + " Retrying... (" + str(farmer_tries) + ")") # logger.warning(str({"log_event_type": "warning", "title": "Shard upload error", # "description": "Error while uploading shard to: " + str( # frame_content["farmer"]["address"] + ":" + str(frame_content["farmer"][ # "port"])) + " Retrying... (" + str(farmer_tries) + ")"})) logger.error(e) continue else: self.emit(SIGNAL("incrementShardsProgressCounters")) # update already uploaded shards count logger.warning(str({"log_event_type": "success", "title": "Uploading shard", "description": "Shard uploaded successfully to " + str( frame_content["farmer"]["address"] + ":" + str(frame_content["farmer"][ "port"]))})) self.emit(SIGNAL("updateUploadTaskState"), rowposition, "Uploaded!") # update shard upload state logger.debug(str(self.all_shards_count) + "wszystkie" + str(self.shards_already_uploaded) + "wyslane") if int(self.all_shards_count) <= int(self.shards_already_uploaded + 1): self.emit(SIGNAL("finishUpload")) # send signal to save to bucket after all files are uploaded break logger.debug(response.content) j = json.loads(str(response.content)) if (j["result"] == "The supplied token is not accepted"): raise storj.exception.StorjFarmerError(storj.exception.StorjFarmerError.SUPPLIED_TOKEN_NOT_ACCEPTED) firstiteration = False it += 1 except storj.exception.StorjBridgeApiError as e: # upload failed due to Storj Bridge failure logger.error("Exception raised while trying to negitiate \ contract: " + str(e)) # logger.warning('"log_event_type": "error"') logger.warning('"title": "Bridge exception"') logger.warning('"description": "Exception raised while trying \ to negotiate storage contract for shard at index\ "' + str(chapters)) # logger.warning(str({"log_event_type": "error", "title": "Bridge exception", # "description": "Exception raised while trying to negitiate storage contract for shard at index " + str( # chapters)})) continue except Exception as e: # now send Exchange Report # upload failed probably while sending data to farmer logger.error("Error occured while trying to upload shard or\ negotiate contract. Retrying... " + str(e)) # logger.warning('"log_event_type": "error"') logger.warning('"title": "Unhandled exception"') logger.warning('"description": "Unhandled exception occured\ while trying to upload shard or negotiate \ contract for shard at index "' + str(chapters) + " . Retrying...") # logger.warning(str({"log_event_type": "error", "title": "Unhandled exception", # "description": "Unhandled exception occured while trying to upload shard or negotiate contract for shard at index " + str(chapters) + " . Retrying..."})) current_timestamp = int(time.time()) exchange_report.exchangeEnd = str(current_timestamp) exchange_report.exchangeResultCode = (exchange_report.FAILURE) exchange_report.exchangeResultMessage = (exchange_report.STORJ_REPORT_UPLOAD_ERROR) self.set_current_status("Sending Exchange Report for shard " + str(chapters + 1)) # self.storj_engine.storj_client.send_exchange_report(exchange_report) # send exchange report continue else: # uploaded with success current_timestamp = int(time.time()) # prepare second half of exchange heport exchange_report.exchangeEnd = str(current_timestamp) exchange_report.exchangeResultCode = (exchange_report.SUCCESS) exchange_report.exchangeResultMessage = (exchange_report.STORJ_REPORT_SHARD_UPLOADED) self.set_current_status("Sending Exchange Report for shard " + str(chapters + 1)) # logger.warning('"log_event_type": "debug"') logger.debug('"title":"Shard added"') logger.info('"description": "Shard "' + str(chapters + 1) + " successfully added and exchange report sent.") # logger.warning(str({"log_event_type": "debug", "title": "Shard added", # "description": "Shard " + str(chapters + 1) + " successfully added and exchange report sent."})) # self.storj_engine.storj_client.send_exchange_report(exchange_report) # send exchange report break