Browse Source

Bugfix PDF Generation in Macros, JasperCompiler CLI #314899

userinfo_gueltigkeit
Daniel Quathamer 9 months ago
parent
commit
a293ea747b
  1. 9
      src/de/superx/bin/SxJasper.java
  2. 101
      src/de/superx/bin/SxJasperCompiler.java
  3. 17
      src/de/superx/servlet/SuperXmlTabelle.java
  4. 4
      superx/WEB-INF/conf/edustore/db/conf/logging.properties
  5. 39
      superx/WEB-INF/conf/edustore/db/conf/module_upgrade.xsl
  6. 30
      superx/WEB-INF/conf/edustore/db/conf/superx2h1.xsl
  7. 2
      superx/WEB-INF/conf/edustore/db/install/conf/builddatum.txt
  8. 30428
      superx/WEB-INF/conf/edustore/db/install/conf/kern.html
  9. 2
      superx/WEB-INF/conf/edustore/db/install/conf/kern.rtf
  10. BIN
      superx/WEB-INF/lib/superx5.0.jar
  11. 7
      superx/xml/tabelle_fo_pdf.xsl

9
src/de/superx/bin/SxJasper.java

@ -51,6 +51,7 @@ import net.sf.jasperreports.engine.util.JRLoader;
Task compile: Quelldatei.jrxml -> Quelldatei.jasper Task compile: Quelldatei.jrxml -> Quelldatei.jasper
Quelldatei.jasper ist kompilierte Beschreibungsdatei für den Bericht Quelldatei.jasper ist kompilierte Beschreibungsdatei für den Bericht
Compile wird ausgeführt, wenn nur eine jrxml-Datei und der Logger übergeben wird.
Task fill/fillIgnorePagination: Quelldatei.jasper -> Quelldatei.jrprint Task fill/fillIgnorePagination: Quelldatei.jasper -> Quelldatei.jrprint
Quelldatei.jrprint enthält mit Daten gefüllte Berichtsvorlage Quelldatei.jrprint enthält mit Daten gefüllte Berichtsvorlage
@ -122,7 +123,7 @@ private static String outfile = "";
} }
GetOpts.setOpts(args); GetOpts.setOpts(args);
String isdrin = GetOpts.isAllRequiredOptionsPresent("-logger,-db_properties,-JRXML"); String isdrin = GetOpts.isAllRequiredOptionsPresent("-logger,-JRXML");
if (isdrin != null) { if (isdrin != null) {
System.err.println("Folgende Optionen fehlen: " + isdrin); System.err.println("Folgende Optionen fehlen: " + isdrin);
System.err.println(usage); System.err.println(usage);
@ -148,8 +149,6 @@ private static String outfile = "";
XSLfileName = GetOpts.getValue("-XSL"); XSLfileName = GetOpts.getValue("-XSL");
if (GetOpts.isPresent("-OUT")) if (GetOpts.isPresent("-OUT"))
outfile = GetOpts.getValue("-OUT"); outfile = GetOpts.getValue("-OUT");
else
outfile=de.memtext.util.FileUtils.getFileNameWithoutSuffix(jrxmlFileName)+".pdf";
if (GetOpts.isPresent("-db_properties")) if (GetOpts.isPresent("-db_properties"))
dbpropfile = GetOpts.getValue("-db_properties"); dbpropfile = GetOpts.getValue("-db_properties");
if (GetOpts.isPresent("-param")) if (GetOpts.isPresent("-param"))
@ -176,7 +175,9 @@ private static String outfile = "";
JasperCompileManager.compileReportToFile(jrxmlFileName,jasperFileName); JasperCompileManager.compileReportToFile(jrxmlFileName,jasperFileName);
logger.info("Compile time : " + (System.currentTimeMillis() - start)); logger.info("Compile time : " + (System.currentTimeMillis() - start));
} }
if (!jasperFileName.equals("") ) if (!jasperFileName.equals("") &&
(GetOpts.isPresent("-db_properties") || GetOpts.isPresent("-XML") )
)
{ {
logger.info("Filling " + jasperFileName); logger.info("Filling " + jasperFileName);

101
src/de/superx/bin/SxJasperCompiler.java

@ -0,0 +1,101 @@
package de.superx.bin;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperPrintManager;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.JRRtfExporter;
import net.sf.jasperreports.engine.export.JRXlsExporter;
import net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory;
import net.sf.jasperreports.engine.util.JRXmlUtils;
import net.sf.jasperreports.engine.util.JRLoader;
/**
* @author Daniel Quathamer Projektgruppe SuperX
*
* @
* Dieses Javaprogramm führt kompiliert JasperReports<br>
*
*Aufruf: java $JAVA_OPTS -cp "$CP" de.superx.bin.SxJasperCompiler <JRXML-Datei>
Achtung: Der Workflow ist wie folgt:
Quelldatei.jrxml ist die XML-Beschreibungsdatei des Berichts
Task compile: Quelldatei.jrxml -> Quelldatei.jasper
Quelldatei.jasper ist kompilierte Beschreibungsdatei für den Bericht
*/
public class SxJasperCompiler {
private static String usage =
"-------------------------------------\nGebrauch: java de.superx.bin.SxJasperCompiler <<jrxml-Datei>> \n---------------------------------------------------";
/**
* @param args
* @throws ClassNotFoundException
* @throws FileNotFoundException
* @throws IOException
* @throws SQLException
*/
public static void go(String args[])
throws
ClassNotFoundException,
FileNotFoundException,
IOException
{
if (args.length== 0) {
throw new IllegalArgumentException("Parameter jrxml-Datei erfoderlich");
}
String jrxmlFileName=args[0];
String jasperFileName=de.memtext.util.FileUtils.getFileNameWithoutSuffix(jrxmlFileName)+".jasper";;
try
{
long start = System.currentTimeMillis();
if (!jrxmlFileName.equals(""))
{
JasperCompileManager.compileReportToFile(jrxmlFileName,jasperFileName);
System.out.println("File "+jasperFileName+" created, compile time : " + (System.currentTimeMillis() - start)+ "ms");
}
}
catch (JRException e)
{
e.printStackTrace();
System.exit(1);
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
System.out.println("Compile erfolgreich beendet");
}
public static void main(String args[]) {
try {
go(args);
} catch (Exception ex) {
System.err.println("Aufruf fehlgeschlagen.\n" + ex);
ex.printStackTrace();
System.exit(1);
}
}
}

17
src/de/superx/servlet/SuperXmlTabelle.java

@ -69,6 +69,7 @@ import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -393,16 +394,19 @@ public class SuperXmlTabelle extends AbstractSuperXServlet {
for (int i = 0; i < rsmd.getColumnCount(); i++) { for (int i = 0; i < rsmd.getColumnCount(); i++) {
coltypes[i] = rsmd.getColumnType(i + 1); coltypes[i] = rsmd.getColumnType(i + 1);
((SXSSFSheet) sh).trackColumnForAutoSizing(i);
} }
// Title // Title
Row row = sh.createRow(rownum); Row row = sh.createRow(rownum);
row.setHeight(Short.parseShort("600"));
Cell cell = row.createCell(0); Cell cell = row.createCell(0);
cell.setCellValue(maske.getName()); cell.setCellValue(maske.getName());
cell.setCellStyle((CellStyle) styles.get("title")); cell.setCellStyle((CellStyle) styles.get("title"));
rownum++; rownum++;
// Legende // Legende
row = sh.createRow(rownum); row = sh.createRow(rownum);
row.setHeight(Short.parseShort("1000"));
cell = row.createCell(0); cell = row.createCell(0);
cell.setCellValue(maske.getLegendText(user)); cell.setCellValue(maske.getLegendText(user));
rownum++; rownum++;
@ -425,6 +429,7 @@ public class SuperXmlTabelle extends AbstractSuperXServlet {
} }
row = sh.createRow(rownum); row = sh.createRow(rownum);
for (int cellnum = 0; cellnum < colcount; cellnum++) { for (int cellnum = 0; cellnum < colcount; cellnum++) {
//((SXSSFSheet) sh).trackColumnForAutoSizing(cellnum);
cell = row.createCell(cellnum); cell = row.createCell(cellnum);
Object o = rs.getObject(cellnum + 1); Object o = rs.getObject(cellnum + 1);
if (o == null) { if (o == null) {
@ -460,7 +465,7 @@ public class SuperXmlTabelle extends AbstractSuperXServlet {
rownum++; rownum++;
} }
for (int i = 0; i < rsmd.getColumnCount(); i++) { for (int i = 1; i < rsmd.getColumnCount(); i++) {
sh.autoSizeColumn(i); sh.autoSizeColumn(i);
} }
// System.out.println(t.getSinceStart()); // System.out.println(t.getSinceStart());
@ -487,6 +492,10 @@ public class SuperXmlTabelle extends AbstractSuperXServlet {
cell = row.createCell(i); cell = row.createCell(i);
cell.setCellValue((String) headers.get(i)); cell.setCellValue((String) headers.get(i));
cell.setCellStyle((CellStyle) styles.get("header")); cell.setCellStyle((CellStyle) styles.get("header"));
((SXSSFSheet) sh).trackColumnForAutoSizing(i);
} }
} }
@ -533,10 +542,10 @@ public class SuperXmlTabelle extends AbstractSuperXServlet {
cellStyle.setDataFormat(createHelper.createDataFormat().getFormat("dd.mm.yyyy")); cellStyle.setDataFormat(createHelper.createDataFormat().getFormat("dd.mm.yyyy"));
styles.put("date", cellStyle); styles.put("date", cellStyle);
cellStyle = wb.createCellStyle(); cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(wb.createDataFormat().getFormat("0,00")); cellStyle.setDataFormat(wb.createDataFormat().getFormat("0.00"));
styles.put("double", cellStyle); styles.put("double", cellStyle);
cellStyle = wb.createCellStyle(); cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(wb.createDataFormat().getFormat("000")); cellStyle.setDataFormat(wb.createDataFormat().getFormat("#"));
styles.put("integer", cellStyle); styles.put("integer", cellStyle);
Font titleFont = wb.createFont(); Font titleFont = wb.createFont();
@ -859,7 +868,7 @@ public class SuperXmlTabelle extends AbstractSuperXServlet {
if (contenttype != null && contenttype.startsWith("application/pdf") && maske.isMaxRowsReached()) { if (contenttype != null && contenttype.startsWith("application/pdf") && maske.isMaxRowsReached()) {
sendBackHtml(de.superx.servlet.SuperXManager.htmlPageHead("Datenproblem") sendBackHtml(de.superx.servlet.SuperXManager.htmlPageHead("Datenproblem")
+ "<h2>Achtung</h2><p>Datenmenge ist zu groß - bitte schränken Sie Ihre Abfrage ggfs. stärker ein</p></body></html>"); + "<h2>Achtung</h2><p>Datenmenge ist zu groß - bitte schränken Sie Ihre Abfrage ggfs. stärker ein</p></body></html>");
} else if (contenttype != null && contenttype.indexOf("excel") > -1 && maske.isMaxRowsReached()) { } else if (contenttype != null && contenttype.indexOf("spreadsheetml") > -1 && maske.isMaxRowsReached()) {
sendBackHtml(de.superx.servlet.SuperXManager.htmlPageHead("Datenproblem") sendBackHtml(de.superx.servlet.SuperXManager.htmlPageHead("Datenproblem")
+ "<center><h2>Achtung</h2><p>Große Datenmenge <br> <input id='ss' type='submit' value='Alle Daten als Exceldatei downloaden' onclick='document.getElementById(\"ss\").disabled=\"true\";location.href=\"/superx/servlet/SuperXmlTabelle?tid=" + "<center><h2>Achtung</h2><p>Große Datenmenge <br> <input id='ss' type='submit' value='Alle Daten als Exceldatei downloaden' onclick='document.getElementById(\"ss\").disabled=\"true\";location.href=\"/superx/servlet/SuperXmlTabelle?tid="
+ maske.getId() + "&bigexcel=true\"'><br><br>(Kann l&auml;nger dauern)</p></center></body></html>"); + maske.getId() + "&bigexcel=true\"'><br><br>(Kann l&auml;nger dauern)</p></center></body></html>");

4
superx/WEB-INF/conf/edustore/db/conf/logging.properties

@ -51,8 +51,8 @@ java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# Limit the message that are printed on the console to INFO and above. # Limit the message that are printed on the console to INFO and above.
#java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.level = WARNING #java.util.logging.ConsoleHandler.level = WARNING
#java.util.logging.ConsoleHandler.level = FINEST #java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

39
superx/WEB-INF/conf/edustore/db/conf/module_upgrade.xsl

@ -456,6 +456,10 @@ drop table <xsl:value-of select="@name" />;
</xsl:if> </xsl:if>
</xsl:for-each> </xsl:for-each>
<xsl:if test="$dbsystem='POSTGRES'">
<xsl:call-template name="upgrade_pkeys" />
</xsl:if>
<xsl:if test="$dbsystem='HSQLDB'"> <xsl:if test="$dbsystem='HSQLDB'">
<xsl:for-each select="/module/joolap-system/dimension-blueprints/dimension-blueprint"> <xsl:for-each select="/module/joolap-system/dimension-blueprints/dimension-blueprint">
<xsl:call-template name="upgrade_blueprint"/> <xsl:call-template name="upgrade_blueprint"/>
@ -464,6 +468,37 @@ drop table <xsl:value-of select="@name" />;
</xsl:template> </xsl:template>
<xsl:template name="upgrade_pkeys">
<xsl:text> -- update primary keys, see ticket: #296420 &#xa;</xsl:text>
<xsl:for-each select="/module/database/table">
<xsl:if test="primaryKeys/rs/row">
<xsl:text>alter table if exists </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text> drop constraint if exists </xsl:text>
<xsl:value-of select="@name"/>_pkey<xsl:text>;&#xa;</xsl:text>
<xsl:text>alter table if exists </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text> drop constraint if exists </xsl:text>
<xsl:value-of select="primaryKeys/rs[1]/row[1]/fld[@name='pk_name']" /><xsl:text>;&#xa;</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="/module/database/table">
<xsl:if test="primaryKeys/rs/row">
<xsl:text>alter table if exists </xsl:text>
<xsl:value-of select="@name" />
<xsl:text> add constraint </xsl:text>
<xsl:value-of select="@name"/>_pkey <xsl:text> primary key (</xsl:text>
<xsl:for-each select="primaryKeys/rs/row/fld[@name='column_name']">
<xsl:value-of select="."/>
<xsl:choose>
<xsl:when test="position() != last()">,</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:text>);&#xa;</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="fields"> <xsl:template name="fields">
<xsl:param name="module" /> <xsl:param name="module" />
<xsl:param name="module_path" /> <xsl:param name="module_path" />
@ -557,7 +592,7 @@ and N.nspname||'.'||C.relname </xsl:text>
</xsl:text> </xsl:text>
<xsl:text><![CDATA[<sqlvars>]]> <xsl:text><![CDATA[<sqlvars>]]>
</xsl:text> </xsl:text>
<xsl:text><![CDATA[<sqlvar name="views" type="list"> <xsl:text><![CDATA[<sqlvar name="views">
select ]]></xsl:text> select ]]></xsl:text>
<xsl:choose> <xsl:choose>
@ -565,7 +600,7 @@ select ]]></xsl:text>
<xsl:text>relname,relname as tabelle from pg_class where relname::varchar(200)</xsl:text> <xsl:text>relname,relname as tabelle from pg_class where relname::varchar(200)</xsl:text>
</xsl:when> </xsl:when>
<xsl:when test="$dbsystem='INFORMIX'"> <xsl:when test="$dbsystem='INFORMIX'">
tabname as tabelle from systables where tabname tabid,tabname as tabelle from systables where tabname
</xsl:when> </xsl:when>
<xsl:when test="$dbsystem='HSQLDB'"> <xsl:when test="$dbsystem='HSQLDB'">
lcase(TABLE_NAME),lcase(TABLE_NAME) as tabelle from SYSTEM_VIEWS where lcase(TABLE_NAME) lcase(TABLE_NAME),lcase(TABLE_NAME) as tabelle from SYSTEM_VIEWS where lcase(TABLE_NAME)

30
superx/WEB-INF/conf/edustore/db/conf/superx2h1.xsl

@ -5,12 +5,13 @@
<xsl:import href="column.xsl" /> <xsl:import href="column.xsl" />
<xsl:import href="view.xsl" /> <xsl:import href="view.xsl" />
<xsl:param name="modul_pfad" /> <xsl:param name="modul_pfad" />
<xsl:param name="dbsystem"/>
<xsl:param name="system">superx</xsl:param> <xsl:param name="system">superx</xsl:param>
<!--<xsl:variable name="dbsystem">POSTGRES</xsl:variable>-->
<!--output method mit NAME text ist wichtig, damit sich result-document darauf beziehen kann--> <!--output method mit NAME text ist wichtig, damit sich result-document darauf beziehen kann-->
<xsl:output method="text" name="text"/> <xsl:output method="text" name="text"/>
<!-- dbconv erwartet Informix-Syntax wird für andere DB-System angepasst--> <!-- dbconv erwartet Informix-Syntax wird für andere DB-System angepasst-->
<xsl:variable name="sx_client">psql</xsl:variable> <xsl:variable name="sx_client">psql</xsl:variable>
<xsl:variable name="dbsystem">POSTGRES</xsl:variable>
<xsl:template match="/"> <xsl:template match="/">
<xsl:value-of select="$modul_pfad" /> <xsl:value-of select="$modul_pfad" />
@ -63,6 +64,12 @@ Generiere <xsl:value-of select="$tabfile"/><xsl:text>
</xsl:variable> </xsl:variable>
<!--alles ist informix konform nur nicht default today, das versteht Postgres nicht--> <!--alles ist informix konform nur nicht default today, das versteht Postgres nicht-->
<xsl:value-of select="replace ($column_str,'default TODAY','default current_date')" /> <xsl:value-of select="replace ($column_str,'default TODAY','default current_date')" />
<xsl:if test="$dbsystem='POSTGRES'">
<!-- add primary key, see ticket: #296420 -->
<xsl:call-template name="primary_key_pg">
<xsl:with-param name="table" select="$table_name"/>
</xsl:call-template>
</xsl:if>
<xsl:text> <xsl:text>
); );
</xsl:text> </xsl:text>
@ -131,6 +138,27 @@ create unique index <xsl:value-of select="row[1]/fld[@name='pk_name']"/> on <xsl
</xsl:for-each> </xsl:for-each>
</xsl:template> </xsl:template>
<!-- end primary index--> <!-- end primary index-->
<!-- start primary_key_pg-->
<xsl:template name="primary_key_pg">
<xsl:param name="table"/>
<xsl:variable name="first_row" select="primaryKeys/rs[1]/row[1]" />
<xsl:variable name="pkname" select="$first_row/fld[@name='pk_name']"/>
<xsl:for-each select="primaryKeys/rs">
<xsl:if test="count(row) &gt; 0 and count(row[1]/fld) &gt; 0">
<xsl:text>&#xa;, primary key (</xsl:text>
<xsl:for-each select="row">
<xsl:value-of select="fld[@name ='column_name']" />
<xsl:if test="position() &lt; last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>)</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<!-- end primary_key_pg -->
<!-- start create_index vergl. /home/superx/db/conf/index.xsl , log ausgabe entfernt--> <!-- start create_index vergl. /home/superx/db/conf/index.xsl , log ausgabe entfernt-->
<xsl:template name="create_index"> <xsl:template name="create_index">
<xsl:text>create </xsl:text> <xsl:text>create </xsl:text>

2
superx/WEB-INF/conf/edustore/db/install/conf/builddatum.txt

@ -1 +1 @@
05.09.2023 25.11.2023

30428
superx/WEB-INF/conf/edustore/db/install/conf/kern.html

File diff suppressed because one or more lines are too long

2
superx/WEB-INF/conf/edustore/db/install/conf/kern.rtf

File diff suppressed because one or more lines are too long

BIN
superx/WEB-INF/lib/superx5.0.jar

Binary file not shown.

7
superx/xml/tabelle_fo_pdf.xsl

@ -183,7 +183,9 @@ die druckbreite_mm und das Attribut page-height anzupassen.
<fo:table-body> <fo:table-body>
<fo:table-row> <fo:table-row>
<fo:table-cell> <fo:table-cell>
<xsl:call-template name="pdftabletitle"/> <xsl:call-template name="pdftabletitle">
<xsl:with-param name="ergebniselement_ordnr" select="$ergebniselement_ordnr" />
</xsl:call-template>
<fo:block margin-bottom="5px"> </fo:block> <fo:block margin-bottom="5px"> </fo:block>
<xsl:call-template name="legende_fo"> <xsl:call-template name="legende_fo">
<xsl:with-param name="ergebniselement_ordnr" select="$ergebniselement_ordnr" /> <xsl:with-param name="ergebniselement_ordnr" select="$ergebniselement_ordnr" />
@ -359,8 +361,9 @@ In FOP ist 1em=ca.20pt, d.h. um zur Schriftgröße 12pt zu kommen, wird Faktor b
</xsl:template> </xsl:template>
<xsl:template name="pdftabletitle"> <xsl:template name="pdftabletitle">
<xsl:param name="ergebniselement_ordnr" select="0" />
<fo:block font-family="{$font_family}" line-height="14pt" font-size="14pt" font-weight="bold" color="rgb(0,0,0)" space-after="2mm"> <fo:block font-family="{$font_family}" line-height="14pt" font-size="14pt" font-weight="bold" color="rgb(0,0,0)" space-after="2mm">
<xsl:value-of select="/ergebnisse/ergebnis/maskenname" /></fo:block> <xsl:value-of select="/ergebnisse/ergebnis [@ordnr=$ergebniselement_ordnr]/maskenname" /></fo:block>
<fo:block font-family="{$font_family}" line-height="{$font_size_normal}" font-size="{$font_size_normal}"> <fo:block font-family="{$font_family}" line-height="{$font_size_normal}" font-size="{$font_size_normal}">
Datensatz vom <xsl:value-of select="/ergebnisse/ergebnis/stand" /> </fo:block> Datensatz vom <xsl:value-of select="/ergebnisse/ergebnis/stand" /> </fo:block>
</xsl:template> </xsl:template>

Loading…
Cancel
Save