def download_csv_file(self, filename): settings = QSettings() settings.setValue( 'Asistente-LADM_COL/wizards/points_csv_file_delimiter', self.txt_delimiter.text().strip()) new_filename, filter = QFileDialog.getSaveFileName( self, QCoreApplication.translate("WizardTranslations", "Save File"), os.path.join( settings.value( 'Asistente-LADM_COL/wizards/points_download_csv_path', '.'), filename), QCoreApplication.translate("WizardTranslations", "CSV File (*.csv *.txt)")) if new_filename: settings.setValue( 'Asistente-LADM_COL/wizards/points_download_csv_path', os.path.dirname(new_filename)) template_file = QFile(":/Asistente-LADM_COL/resources/csv/" + filename) if not template_file.exists(): self.logger.critical( __name__, "CSV doesn't exist! Probably due to a missing 'make' execution to generate resources..." ) msg = QCoreApplication.translate( "WizardTranslations", "CSV file not found. Update your plugin. For details see log." ) self.show_message(msg, Qgis.Warning) return if os.path.isfile(new_filename): self.logger.info( __name__, 'Removing existing file {}...'.format(new_filename)) os.chmod(new_filename, 0o777) os.remove(new_filename) if template_file.copy(new_filename): os.chmod( new_filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) msg = QCoreApplication.translate( "WizardTranslations", """The file <a href="file:///{}">{}</a> was successfully saved!""" ).format(normalize_local_url(new_filename), os.path.basename(new_filename)) self.show_message(msg, Qgis.Info) else: self.logger.warning( __name__, 'There was an error copying the CSV file {}!'.format( new_filename)) msg = QCoreApplication.translate( "WizardTranslations", "The file couldn\'t be saved.") self.show_message(msg, Qgis.Warning)
def _save_dependency_file(self, fetcher_task): self._downloading = False if fetcher_task.reply() is not None: try: if not os.path.exists(DEPENDENCY_CRYPTO_DIR): os.makedirs(DEPENDENCY_CRYPTO_DIR) # Write response to tmp file out_file = QFile(CRYPTO_LIBRARY_PATH) out_file.open(QIODevice.WriteOnly) out_file.write(fetcher_task.reply().readAll()) out_file.close() except PermissionError as e: self.logger.warning_msg( __name__, QCoreApplication.translate( "EncrypterDecrypter", "The dependency used to encrypt/decrypt couldn't be installed. Check if it is possible to write into this folder: <a href='file:///{path}'>{path}</a>" ).format(path=normalize_local_url(DEPENDENCY_CRYPTO_DIR))) else: self.logger.clear_message_bar() self.logger.info_msg( __name__, QCoreApplication.translate( "EncrypterDecrypter", "The dependency used to encrypt/decrypt is properly installed!" ))
def download_excel_file(self, filename): settings = QSettings() new_filename, filter = QFileDialog.getSaveFileName( self, QCoreApplication.translate("ImportFromExcelDialog", "Save File"), os.path.join( settings.value( 'Asistente-LADM-COL/import_from_excel_dialog/template_save_path', '.'), filename), QCoreApplication.translate("ImportFromExcelDialog", "Excel File (*.xlsx *.xls)")) if new_filename: settings.setValue( 'Asistente-LADM-COL/import_from_excel_dialog/template_save_path', os.path.dirname(new_filename)) template_file = QFile(":/Asistente-LADM-COL/resources/excel/" + filename) if not template_file.exists(): self.logger.critical( __name__, "Excel doesn't exist! Probably due to a missing 'make' execution to generate resources..." ) msg = QCoreApplication.translate( "ImportFromExcelDialog", "Excel file not found. Update your plugin. For details see log." ) self.show_message(msg, Qgis.Warning) return if os.path.isfile(new_filename): self.logger.info( __name__, 'Removing existing file {}...'.format(new_filename)) os.chmod(new_filename, 0o777) os.remove(new_filename) if template_file.copy(new_filename): os.chmod( new_filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) msg = QCoreApplication.translate( "ImportFromExcelDialog", """The file <a href="file:///{}">{}</a> was successfully saved!""" ).format(normalize_local_url(new_filename), os.path.basename(new_filename)) self.show_message(msg, Qgis.Info) else: self.logger.info( __name__, 'There was an error copying the CSV file {}!'.format( new_filename)) msg = QCoreApplication.translate( "ImportFromExcelDialog", "The file couldn\'t be saved.") self.show_message(msg, Qgis.Warning)
def validate_qrs(self): if self.__qr_engine is None: self.__qr_engine = QualityRuleEngine(self.__db, self.__selected_qrs, self.app.settings.tolerance, self.__qr_results_dir_path) self.__qr_engine.progress_changed.connect( self.total_progress_changed) else: self.__qr_engine.initialize(self.__db, self.__selected_qrs, self.app.settings.tolerance, self.__qr_results_dir_path) #self.__qr_engine.qr_logger.show_message_emitted.connect(self.show_log_quality_message) #self.__qr_engine.qr_logger.show_button_emitted.connect(self.show_log_quality_button) #self.__qr_engine.qr_logger.set_initial_progress_emitted.connect(self.set_log_quality_initial_progress) #self.__qr_engine.qr_logger.set_final_progress_emitted.connect(self.set_log_quality_final_progress) use_roads = bool(QSettings().value( 'Asistente-LADM-COL/quality/use_roads', DEFAULT_USE_ROADS_VALUE, bool)) options = {QR_IGACR3006: {'use_roads': use_roads}} res, msg, qrs_res = self.__qr_engine.validate_quality_rules(options) if not res: return res, msg, None self.__qrs_results = qrs_res self.__connect_layer_willbedeleted_signals( ) # Note: Call it after validate_quality_rules! res_u, msg_u, output_qr_dir = QualityErrorDBUtils.get_quality_validation_output_path( self.__qr_results_dir_path, self.__qr_engine.get_timestamp()) if len(self.__selected_qrs) == 1: pre_text = QCoreApplication.translate( "QualityRules", "The quality rule was checked!") else: pre_text = QCoreApplication.translate( "QualityRules", "All the {} quality rules were checked!").format( len(self.__selected_qrs)) post_text = QCoreApplication.translate( "QualityRules", "Both a PDF report and a GeoPackage database with errors can be found in <a href='file:///{}'>{}</a>." ).format(normalize_local_url(output_qr_dir), output_qr_dir) self.logger.success_msg(__name__, "{} {}".format(pre_text, post_text)) self.__emit_refresh_error_layer_symbology() return res, msg, self.__qrs_results
def export_field_data(self, export_dir): names = self._db.names # Get list of basket t_ili_tids to export receivers_data = self._ladm_data.get_fdc_receivers_data( names, self.user_layer(), self._get_receiver_referenced_field(), self.receiver_type, full_name=False) receiver_idx = self.parcel_layer().fields().indexOf( self._get_parcel_field_referencing_receiver()) receiver_ids = self.parcel_layer().uniqueValues(receiver_idx) # Only t_basket of receivers with allocated parcels basket_t_ids = [k for k in receivers_data.keys() if k in receiver_ids] basket_table = self._ladm_data.get_basket_table(self._db) basket_dict = { f[names.T_ILI_TID_F]: receivers_data[f[names.T_ID_F]][0] for f in basket_table.getFeatures() if f[self._db.names.T_ID_F] in basket_t_ids } if not basket_dict: return False, QCoreApplication.translate( "FieldDataCaptureAdminController", "First allocate parcels to at least one coordinator.") # Now set basket id for allocated parcels' related features res = self._ladm_data.set_basket_for_features_related_to_allocated_parcels_field_data_capture( self._db, self.receiver_type, self._get_parcel_field_referencing_receiver(), self._get_receiver_referenced_field(), self.parcel_layer(), self.plot_layer(), self.user_layer()) # Finally, export each basket to XTF basket_exporter = BasketExporter(self._db, basket_dict, export_dir) basket_exporter.total_progress_updated.connect( self.export_field_data_progress) # Signal chaining all_res = basket_exporter.export_baskets() for basket, res in all_res.items(): if not res[0]: # res: (bool, msg) return res return True, QCoreApplication.translate( "FieldDataCaptureAdminController", "{count} XTFs were succcessfully generated in <a href='file:///{normalized_path}'>{path}</a>!" ).format(count=len(basket_dict), normalized_path=normalize_local_url(export_dir), path=export_dir)
def _save_dependency_file(self, fetcher_task): if fetcher_task.reply() is not None: # Write response to tmp file tmp_file = tempfile.mktemp() out_file = QFile(tmp_file) out_file.open(QIODevice.WriteOnly) out_file.write(fetcher_task.reply().readAll()) out_file.close() if not os.path.exists(DEPENDENCIES_BASE_PATH): os.makedirs(DEPENDENCIES_BASE_PATH) if md5sum(tmp_file) == DICT_JAVA_MD5SUM[KEY_JAVA_OS_VERSION]: try: tar = tarfile.open(tmp_file) tar.extractall(DEPENDENCIES_BASE_PATH) tar.close() except tarfile.ReadError as e: self.logger.warning_msg( __name__, QCoreApplication.translate( "JavaDependency", "There was an error with the download. The downloaded file is invalid." )) except PermissionError as e: self.logger.warning_msg( __name__, QCoreApplication.translate( "JavaDependency", "Java couldn't be installed. Check if it is possible to write into this folder: <a href='file:///{path}'>{path}</a>" ).format(path=normalize_local_url( os.path.join( DEPENDENCIES_BASE_PATH, DICT_JAVA_DIR_NAME[KEY_JAVA_OS_VERSION])))) else: self.logger.warning_msg( __name__, QCoreApplication.translate( "JavaDependency", "There was an error with the download. The downloaded file is invalid." )) try: os.remove(tmp_file) except: pass self._downloading = False
def save_dependency_file(self, fetcher_task): if fetcher_task.reply() is not None: # Write response to tmp file tmp_file = tempfile.mktemp() out_file = QFile(tmp_file) out_file.open(QIODevice.WriteOnly) out_file.write(fetcher_task.reply().readAll()) out_file.close() if not os.path.exists(DEPENDENCIES_BASE_PATH): os.makedirs(DEPENDENCIES_BASE_PATH) try: with zipfile.ZipFile(tmp_file, "r") as zip_ref: zip_ref.extractall(DEPENDENCIES_BASE_PATH) except zipfile.BadZipFile as e: self.logger.warning_msg( __name__, QCoreApplication.translate( "ReportGenerator", "There was an error with the download. The downloaded file is invalid." )) except PermissionError as e: self.logger.warning_msg( __name__, QCoreApplication.translate( "ReportGenerator", "Dependencies to generate reports couldn't be installed. Check if it is possible to write into this folder: <a href='file:///{path}'>{path}</a>" ).format(path=normalize_local_url( os.path.join(DEPENDENCIES_BASE_PATH), DEPENDENCY_REPORTS_DIR_NAME))) else: self.logger.info_msg( __name__, QCoreApplication.translate( "ReportGenerator", "The dependency to generate reports is properly installed! Select plots and click again the button in the toolbar to generate reports." )) try: os.remove(tmp_file) except: pass self._downloading = False
def remove_dependency_directory(dir_name_dependency): """ We need to get rid of dependencies when they don't match the version that should be installed for this version of the plugin. """ base_path = os.path.join(DEPENDENCIES_BASE_PATH, dir_name_dependency) # Since folders might contain read only files, we need to delete them # using a callback (see https://docs.python.org/3/library/shutil.html#rmtree-example) shutil.rmtree(base_path, onerror=remove_readonly) Logger().clear_message_bar() if os.path.exists(base_path): Logger().warning_msg( __name__, QCoreApplication.translate( "Utils", "It wasn't possible to remove the dependency folder. You need to remove this folder yourself to generate reports: <a href='file:///{path}'>{path}</a>" ).format(path=normalize_local_url(base_path)))
def load_lis_files(self, alphanumeric_file_paths): root = QgsProject.instance().layerTreeRoot() alphanumeric_group = root.addGroup( QCoreApplication.translate("MissingSuppliesBaseDialog", "LIS Supplies")) for name in alphanumeric_file_paths: uri = 'file:///{}?type=csv&delimiter=;&detectTypes=yes&geomType=none&subsetIndex=no&watchFile=no'.format( normalize_local_url(alphanumeric_file_paths[name])) layer = QgsVectorLayer(uri, name, 'delimitedtext') if layer.isValid(): self.alphanumeric_file_paths[name] = layer QgsProject.instance().addMapLayer(layer, False) alphanumeric_group.addLayer(layer) else: return False, QCoreApplication.translate( "MissingSuppliesBaseDialog", "There were troubles loading the LIS file called '{}'.". format(name)) return True, ''
def accepted(self): self.bar.clearWidgets() self.save_settings(self.data_system) self.folder_path = self.txt_file_path_folder_supplies.text() self.file_names = self.txt_file_names_supplies.text().strip() self.gpkg_path = os.path.join(self.folder_path, '{}.gpkg'.format(self.file_names)) self.xlsx_path = os.path.join(self.folder_path, '{}.xlsx'.format(self.file_names)) reply = self.validate_files_in_folder() csv_paths = {'PREDIO': self.txt_file_path_predio.text().strip()} required_layers = [ 'R_TERRENO', 'U_TERRENO', 'R_VEREDA', 'U_MANZANA', 'R_CONSTRUCCION', 'U_CONSTRUCCION', 'U_UNIDAD', 'R_UNIDAD' ] if reply == QMessageBox.Yes: with OverrideCursor(Qt.WaitCursor): self.set_gui_controls_enabled(False) res_csv, msg_csv = self.load_csv_files(csv_paths) if res_csv: res_gdb, msg_gdb = self.load_gdb_files(required_layers) if res_gdb: self._running_tool = True res_etl, msg_etl = self.run_model_missing_snc_supplies( ) self.progress_base = 100 # We start counting a second step from 100 if res_etl: # Since we have two steps, we need to check at this point if the user already canceled if not self.custom_feedback.isCanceled(): self.logger.clear_status() res_gpkg, msg_gpkg = self.package_results( self.output_etl_missing_snc) if res_gpkg: self.generate_excel_report() if not self.custom_feedback.isCanceled(): self.progress.setValue( self.progress_maximum) self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton( QDialogButtonBox.Close) msg = QCoreApplication.translate( "Asistente-LADM-COL", "Missing supplies report successfully generated in folder <a href='file:///{normalized_path1}'>{path1}</a>! The output Geopackage database can be found in <a href='file:///{normalized_path2}'>{path2}</a>" ).format(normalized_path1= normalize_local_url( self.xlsx_path), path1=self.xlsx_path, normalized_path2= normalize_local_url( self.gpkg_path), path2=self.gpkg_path) self.logger.clear_status() self.logger.success_msg(__name__, msg) else: self.initialize_feedback( ) # Get ready for an eventual new execution self.progress_base = 0 self.logger.clear_status() self._running_tool = False else: # User could have canceled while running the second algorithm if self.custom_feedback.isCanceled(): self.initialize_feedback( ) # Get ready for an eventual new execution self.progress_base = 0 self._running_tool = False else: self.show_message( msg_gpkg, Qgis.Warning) else: # User canceled in the first algorithm self.initialize_feedback() self.progress_base = 0 self._running_tool = False self.logger.clear_status() else: self.show_message(msg_etl, Qgis.Warning) else: self.show_message(msg_gdb, Qgis.Warning) else: self.show_message(msg_csv, Qgis.Warning) self.set_gui_controls_enabled(True)
def generate_report(self, db, report_type): # Check if mapfish and Jasper are installed, otherwise show where to # download them from and return if not self.report_dependency.check_if_dependency_is_valid(): self.report_dependency.download_dependency(URL_REPORTS_LIBRARIES) return java_home_set = self.java_dependency.set_java_home() if not java_home_set: self.java_dependency.get_java_on_demand() self.logger.info_msg( __name__, QCoreApplication.translate( "ReportGenerator", "Java is a prerequisite. Since it was not found, it is being configured..." )) return plot_layer = self.app.core.get_layer(db, db.names.LC_PLOT_T, load=True) if not plot_layer: return selected_plots = plot_layer.selectedFeatures() if not selected_plots: self.logger.warning_msg( __name__, QCoreApplication.translate( "ReportGenerator", "To generate reports, first select at least a plot!")) return # Where to store the reports? previous_folder = QSettings().value( "Asistente-LADM-COL/reports/save_into_dir", ".") save_into_folder = QFileDialog.getExistingDirectory( None, QCoreApplication.translate( "ReportGenerator", "Select a folder to save the reports to be generated"), previous_folder) if not save_into_folder: self.logger.warning_msg( __name__, QCoreApplication.translate( "ReportGenerator", "You need to select a folder where to save the reports before continuing." )) return QSettings().setValue("Asistente-LADM-COL/reports/save_into_dir", save_into_folder) config_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, report_type) json_spec_file = os.path.join(config_path, 'spec_json_file.json') script_name = '' if os.name == 'posix': script_name = 'print' elif os.name == 'nt': script_name = 'print.bat' script_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, 'bin', script_name) if not os.path.isfile(script_path): self.logger.warning( __name__, "Script file for reports wasn't found! {}".format(script_path)) return self.enable_action_requested.emit(report_type, False) # Update config file yaml_config_path = self.update_yaml_config(db, config_path) self.logger.debug( __name__, "Config file for reports: {}".format(yaml_config_path)) total = len(selected_plots) step = 0 count = 0 tmp_dir = self.get_tmp_dir() # Progress bar setup progress = QProgressBar() if total == 1: progress.setRange(0, 0) else: progress.setRange(0, 100) progress.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.app.gui.create_progress_message_bar( QCoreApplication.translate("ReportGenerator", "Generating {} report{}...").format( total, '' if total == 1 else 's'), progress) polygons_with_holes = [] multi_polygons = [] for selected_plot in selected_plots: plot_id = selected_plot[db.names.T_ID_F] geometry = selected_plot.geometry() abstract_geometry = geometry.get() if abstract_geometry.ringCount() > 1: polygons_with_holes.append(str(plot_id)) self.logger.warning( __name__, QCoreApplication.translate( "ReportGenerator", "Skipping Annex 17 for plot with {}={} because it has holes. The reporter module does not support such polygons." ).format(db.names.T_ID_F, plot_id)) continue if abstract_geometry.numGeometries() > 1: multi_polygons.append(str(plot_id)) self.logger.warning( __name__, QCoreApplication.translate( "ReportGenerator", "Skipping Annex 17 for plot with {}={} because it is a multi-polygon. The reporter module does not support such polygons." ).format(db.names.T_ID_F, plot_id)) continue # Generate data file json_file = self.update_json_data(db, json_spec_file, plot_id, tmp_dir, report_type) self.logger.debug(__name__, "JSON file for reports: {}".format(json_file)) # Run sh/bat passing config and data files proc = QProcess() proc.readyReadStandardError.connect( functools.partial(self.stderr_ready, proc=proc)) proc.readyReadStandardOutput.connect( functools.partial(self.stdout_ready, proc=proc)) parcel_number = self.ladm_data.get_parcels_related_to_plots( db, [plot_id], db.names.LC_PARCEL_T_PARCEL_NUMBER_F) or [''] file_name = '{}_{}_{}.pdf'.format(report_type, plot_id, parcel_number[0]) current_report_path = os.path.join(save_into_folder, file_name) proc.start(script_path, [ '-config', yaml_config_path, '-spec', json_file, '-output', current_report_path ]) if not proc.waitForStarted(): # Grant execution permissions os.chmod( script_path, stat.S_IXOTH | stat.S_IXGRP | stat.S_IXUSR | stat.S_IRUSR | stat.S_IRGRP) proc.start(script_path, [ '-config', yaml_config_path, '-spec', json_file, '-output', current_report_path ]) if not proc.waitForStarted(): proc = None self.logger.warning( __name__, "Couldn't execute script to generate report...") else: loop = QEventLoop() proc.finished.connect(loop.exit) loop.exec() self.logger.debug(__name__, "{}:{}".format(plot_id, proc.exitCode())) if proc.exitCode() == 0: count += 1 step += 1 progress.setValue(step * 100 / total) os.remove(yaml_config_path) self.enable_action_requested.emit(report_type, True) self.logger.clear_message_bar() if total == count: if total == 1: msg = QCoreApplication.translate( "ReportGenerator", "The report <a href='file:///{}'>{}</a> was successfully generated!" ).format(normalize_local_url(save_into_folder), file_name) else: msg = QCoreApplication.translate( "ReportGenerator", "All reports were successfully generated in folder <a href='file:///{path}'>{path}</a>!" ).format(path=normalize_local_url(save_into_folder)) self.logger.success_msg(__name__, msg) else: details_msg = '' if polygons_with_holes: details_msg += QCoreApplication.translate( "ReportGenerator", " The following polygons were skipped because they have holes and are not supported: {}." ).format(", ".join(polygons_with_holes)) if multi_polygons: details_msg += QCoreApplication.translate( "ReportGenerator", " The following polygons were skipped because they are multi-polygons and are not supported: {}." ).format(", ".join(multi_polygons)) if total == 1: msg = QCoreApplication.translate( "ReportGenerator", "The report for plot {} couldn't be generated!{} See QGIS log (tab '{}') for details." ).format(plot_id, details_msg, self.LOG_TAB) else: if count == 0: msg = QCoreApplication.translate( "ReportGenerator", "No report could be generated!{} See QGIS log (tab '{}') for details." ).format(details_msg, self.LOG_TAB) else: msg = QCoreApplication.translate( "ReportGenerator", "At least one report couldn't be generated!{details_msg} See QGIS log (tab '{log_tab}') for details. Go to <a href='file:///{path}'>{path}</a> to see the reports that were generated." ).format(details_msg=details_msg, path=normalize_local_url(save_into_folder), log_tab=self.LOG_TAB) self.logger.warning_msg(__name__, msg)
def load_alphanumeric_layers(self): self.alphanumeric_file_paths = { 'blo': self.data_source_widget.txt_file_path_blo.text().strip(), 'uni': self.data_source_widget.txt_file_path_uni.text().strip(), 'ter': self.data_source_widget.txt_file_path_ter.text().strip(), 'pro': self.data_source_widget.txt_file_path_pro.text().strip() } root = QgsProject.instance().layerTreeRoot() lis_group = root.addGroup(QCoreApplication.translate(self.CLASS_NAME, "LIS Supplies")) for name in self.alphanumeric_file_paths: uri = 'file:///{}?encoding=latin1&type=csv&delimiter=;"e=&escape=&detectTypes=yes&geomType=none&subsetIndex=no&watchFile=no'.format(normalize_local_url(self.alphanumeric_file_paths[name])) layer = QgsVectorLayer(uri, name, 'delimitedtext') if layer.isValid(): self.alphanumeric_file_paths[name] = layer QgsProject.instance().addMapLayer(layer, False) lis_group.addLayer(layer) else: if name == 'blo': # BLO is kind of optional, if it is not given, we pass a default one uri = 'file:///{}?encoding=latin1&type=csv&delimiter=;"e=&escape=&detectTypes=yes&geomType=none&subsetIndex=no&watchFile=no'.format(normalize_local_url(BLO_LIS_FILE_PATH)) layer = QgsVectorLayer(uri, name, 'delimitedtext') self.alphanumeric_file_paths[name] = layer QgsProject.instance().addMapLayer(layer, False) lis_group.addLayer(layer) else: return False, QCoreApplication.translate(self.CLASS_NAME, "There were troubles loading the LIS file called '{}'.".format(name)) return True, ''
def convert_to_offline(self, db, surveyor_expression_dict, export_dir): sys.path.append(PLUGINS_DIR) from qfieldsync.core.layer import LayerSource, SyncAction from qfieldsync.core.offline_converter import OfflineConverter from qfieldsync.core.project import ProjectConfiguration project = QgsProject.instance() extent = QgsRectangle() offline_editing = QgsOfflineEditing() # Configure project project_configuration = ProjectConfiguration(project) project_configuration.create_base_map = False project_configuration.offline_copy_only_aoi = False project_configuration.use_layer_selection = True # Layer config layer_sync_action = LayerConfig.get_field_data_capture_layer_config( db.names) total_projects = len(surveyor_expression_dict) current_progress = 0 for surveyor, layer_config in surveyor_expression_dict.items(): export_folder = os.path.join(export_dir, surveyor) # Get layers (cannot be done out of this for loop because the project is closed and layers are deleted) layers = { layer_name: None for layer_name, _ in layer_sync_action.items() } self.app.core.get_layers(db, layers, True) if not layers: return False, QCoreApplication.translate( "FieldDataCapture", "At least one layer could not be found.") # Configure layers for layer_name, layer in layers.items(): layer_source = LayerSource(layer) layer_source.action = layer_sync_action[layer_name] if layer_name in layer_config: layer_source.select_expression = layer_config[layer_name] layer_source.apply() offline_converter = OfflineConverter(project, export_folder, extent, offline_editing) offline_converter.convert() offline_editing.layerProgressUpdated.disconnect( offline_converter.on_offline_editing_next_layer) offline_editing.progressModeSet.disconnect( offline_converter.on_offline_editing_max_changed) offline_editing.progressUpdated.disconnect( offline_converter.offline_editing_task_progress) current_progress += 1 self.total_progress_updated.emit( int(100 * current_progress / total_projects)) return True, QCoreApplication.translate( "FieldDataCapture", "{count} offline projects have been successfully created in <a href='file:///{normalized_path}'>{path}</a>!" ).format(count=total_projects, normalized_path=normalize_local_url(export_dir), path=export_dir)