IDEMPIERE-4969 Clean non-working 2Pack Code for File and CodeSnippet (#886)
This commit is contained in:
parent
5413f0c11d
commit
078d130cd7
|
@ -0,0 +1,15 @@
|
||||||
|
SET SQLBLANKLINES ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
|
||||||
|
-- IDEMPIERE-4969 Clean non-working 2Pack Code for File and CodeSnippet
|
||||||
|
-- Sep 17, 2021, 5:51:16 PM CEST
|
||||||
|
UPDATE AD_Ref_List SET IsActive='N',Updated=TO_DATE('2021-09-17 17:51:16','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=50021
|
||||||
|
;
|
||||||
|
|
||||||
|
-- Sep 17, 2021, 5:51:35 PM CEST
|
||||||
|
UPDATE AD_Ref_List SET Name='Code Snippet', Description='Replace a code snippet with in a file', IsActive='N',Updated=TO_DATE('2021-09-17 17:51:35','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=50029
|
||||||
|
;
|
||||||
|
|
||||||
|
SELECT register_migration_script('202109171752_IDEMPIERE-4969.sql') FROM dual
|
||||||
|
;
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- IDEMPIERE-4969 Clean non-working 2Pack Code for File and CodeSnippet
|
||||||
|
-- Sep 17, 2021, 5:51:16 PM CEST
|
||||||
|
UPDATE AD_Ref_List SET IsActive='N',Updated=TO_TIMESTAMP('2021-09-17 17:51:16','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=50021
|
||||||
|
;
|
||||||
|
|
||||||
|
-- Sep 17, 2021, 5:51:35 PM CEST
|
||||||
|
UPDATE AD_Ref_List SET Name='Code Snippet', Description='Replace a code snippet with in a file', IsActive='N',Updated=TO_TIMESTAMP('2021-09-17 17:51:35','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=50029
|
||||||
|
;
|
||||||
|
|
||||||
|
SELECT register_migration_script('202109171752_IDEMPIERE-4969.sql') FROM dual
|
||||||
|
;
|
||||||
|
|
|
@ -87,8 +87,6 @@ public class MPackageExpDetail extends X_AD_Package_Exp_Detail
|
||||||
String type = getType();
|
String type = getType();
|
||||||
if (TYPE_ApplicationOrModule.equals(type)) {
|
if (TYPE_ApplicationOrModule.equals(type)) {
|
||||||
return getAD_Menu_ID();
|
return getAD_Menu_ID();
|
||||||
} else if (TYPE_CodeSnipit.equals(type)) {
|
|
||||||
return 0;
|
|
||||||
} else if (TYPE_Data.equals(type)) {
|
} else if (TYPE_Data.equals(type)) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (TYPE_DataSingle.equals(type)) {
|
} else if (TYPE_DataSingle.equals(type)) {
|
||||||
|
|
|
@ -924,8 +924,6 @@ public class X_AD_Package_Exp_Detail extends PO implements I_AD_Package_Exp_Deta
|
||||||
public static final int TYPE_AD_Reference_ID=50004;
|
public static final int TYPE_AD_Reference_ID=50004;
|
||||||
/** Workbench = B */
|
/** Workbench = B */
|
||||||
public static final String TYPE_Workbench = "B";
|
public static final String TYPE_Workbench = "B";
|
||||||
/** File - Code or other = C */
|
|
||||||
public static final String TYPE_File_CodeOrOther = "C";
|
|
||||||
/** Data = D */
|
/** Data = D */
|
||||||
public static final String TYPE_Data = "D";
|
public static final String TYPE_Data = "D";
|
||||||
/** Data Single = DS */
|
/** Data Single = DS */
|
||||||
|
@ -954,8 +952,6 @@ public class X_AD_Package_Exp_Detail extends PO implements I_AD_Package_Exp_Deta
|
||||||
public static final String TYPE_Reference = "REF";
|
public static final String TYPE_Reference = "REF";
|
||||||
/** Role = S */
|
/** Role = S */
|
||||||
public static final String TYPE_Role = "S";
|
public static final String TYPE_Role = "S";
|
||||||
/** Code Snipit = SNI */
|
|
||||||
public static final String TYPE_CodeSnipit = "SNI";
|
|
||||||
/** SQL Statement = SQL */
|
/** SQL Statement = SQL */
|
||||||
public static final String TYPE_SQLStatement = "SQL";
|
public static final String TYPE_SQLStatement = "SQL";
|
||||||
/** SQL Mandatory = SQM */
|
/** SQL Mandatory = SQM */
|
||||||
|
|
|
@ -8,10 +8,6 @@
|
||||||
class="org.adempiere.pipo2.handler.AdElementHandler"
|
class="org.adempiere.pipo2.handler.AdElementHandler"
|
||||||
id="AD_Element">
|
id="AD_Element">
|
||||||
</handler>
|
</handler>
|
||||||
<handler
|
|
||||||
class="org.adempiere.pipo2.handler.CodeSnippetElementHandler"
|
|
||||||
id="Code_Snippet">
|
|
||||||
</handler>
|
|
||||||
<handler
|
<handler
|
||||||
class="org.adempiere.pipo2.handler.ColumnElementHandler"
|
class="org.adempiere.pipo2.handler.ColumnElementHandler"
|
||||||
id="AD_Column">
|
id="AD_Column">
|
||||||
|
@ -124,10 +120,6 @@
|
||||||
class="org.adempiere.pipo2.handler.TaskAccessElementHandler"
|
class="org.adempiere.pipo2.handler.TaskAccessElementHandler"
|
||||||
id="AD_Task_Access">
|
id="AD_Task_Access">
|
||||||
</handler>
|
</handler>
|
||||||
<handler
|
|
||||||
class="org.adempiere.pipo2.handler.DistFileElementHandler"
|
|
||||||
id="Dist_File">
|
|
||||||
</handler>
|
|
||||||
<handler
|
<handler
|
||||||
class="org.adempiere.pipo2.handler.ReportViewElementHandler"
|
class="org.adempiere.pipo2.handler.ReportViewElementHandler"
|
||||||
id="AD_ReportView">
|
id="AD_ReportView">
|
||||||
|
|
|
@ -1,248 +0,0 @@
|
||||||
/******************************************************************************
|
|
||||||
* Product: Adempiere ERP & CRM Smart Business Solution *
|
|
||||||
* Copyright (C) 1999-2006 Adempiere, Inc. All Rights Reserved. *
|
|
||||||
* This program is free software; you can redistribute it and/or modify it *
|
|
||||||
* under the terms version 2 of the GNU General Public License as published *
|
|
||||||
* by the Free Software Foundation. This program is distributed in the hope *
|
|
||||||
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
||||||
* See the GNU General Public License for more details. *
|
|
||||||
* You should have received a copy of the GNU General Public License along *
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
|
||||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 Robert Klein. robeklein@hotmail.com
|
|
||||||
* Contributor(s): Low Heng Sin hengsin@avantz.com
|
|
||||||
*****************************************************************************/
|
|
||||||
package org.adempiere.pipo2.handler;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import javax.xml.transform.sax.TransformerHandler;
|
|
||||||
|
|
||||||
|
|
||||||
import org.compiere.Adempiere;
|
|
||||||
import org.compiere.model.X_AD_Package_Imp_Backup;
|
|
||||||
import org.compiere.model.X_AD_Package_Imp_Detail;
|
|
||||||
|
|
||||||
import org.compiere.util.Env;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import org.xml.sax.helpers.AttributesImpl;
|
|
||||||
|
|
||||||
import org.adempiere.pipo2.AbstractElementHandler;
|
|
||||||
import org.adempiere.pipo2.CodeSnippetElementParameters;
|
|
||||||
import org.adempiere.pipo2.Element;
|
|
||||||
import org.adempiere.pipo2.PIPOContext;
|
|
||||||
import org.adempiere.pipo2.PackOut;
|
|
||||||
import org.adempiere.pipo2.PackoutItem;
|
|
||||||
|
|
||||||
public class CodeSnippetElementHandler extends AbstractElementHandler {
|
|
||||||
|
|
||||||
public void startElement(PIPOContext ctx, Element element) throws SAXException {
|
|
||||||
String action = null;
|
|
||||||
action = "Update";
|
|
||||||
String releaseNumber = getStringValue(element, "ReleaseNo");
|
|
||||||
//Check Release Number
|
|
||||||
if(Adempiere.MAIN_VERSION.equals(releaseNumber)||releaseNumber.equals("all")){
|
|
||||||
String sourceName = getStringValue(element, "filename");
|
|
||||||
String targetDirectory = getStringValue(element, "filedir");
|
|
||||||
String oldCode = getStringValue(element, "oldcode");
|
|
||||||
String newCode = getStringValue(element, "newcode");
|
|
||||||
|
|
||||||
InputStream source; // Stream for reading from the source file.
|
|
||||||
OutputStream copy; // Stream for writing the copy.
|
|
||||||
|
|
||||||
String packagePath=null;
|
|
||||||
String sourcePath=null;
|
|
||||||
|
|
||||||
//get adempiere-all directory
|
|
||||||
try {
|
|
||||||
packagePath = getPackageDirectory(ctx.ctx);
|
|
||||||
File parentDirectory = new File(packagePath);
|
|
||||||
while (!parentDirectory.getName().equals("packages")){
|
|
||||||
parentDirectory = parentDirectory.getParentFile();
|
|
||||||
}
|
|
||||||
parentDirectory = parentDirectory.getParentFile();
|
|
||||||
sourcePath = parentDirectory.getCanonicalPath();
|
|
||||||
} catch (IOException e1) {
|
|
||||||
System.out.println("Can't find adempiere-all directory.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create backup directory if required
|
|
||||||
File backupDir = new File(packagePath+File.separator+"backup"+File.separator);
|
|
||||||
if (!backupDir.exists()){
|
|
||||||
boolean success = (new File(packagePath+File.separator+"backup"+File.separator)).mkdirs();
|
|
||||||
if (!success) {
|
|
||||||
log.info("Backup directory creation failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Correct target directory for proper file seperator
|
|
||||||
String fullDirectory = sourcePath+targetDirectory;
|
|
||||||
String targetDirectoryModified=null;
|
|
||||||
String fileDate = null;
|
|
||||||
char slash1 = '\\';
|
|
||||||
char slash2 = '/';
|
|
||||||
if (File.separator.equals("/"))
|
|
||||||
targetDirectoryModified = fullDirectory.replace(slash1,slash2);
|
|
||||||
else
|
|
||||||
targetDirectoryModified = fullDirectory.replace(slash2,slash1);
|
|
||||||
|
|
||||||
File file = new File(targetDirectoryModified+sourceName);
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info(targetDirectoryModified+sourceName);
|
|
||||||
//TODO: derived force from user parameter
|
|
||||||
boolean force = true;
|
|
||||||
// check to see if overwrites are allowed
|
|
||||||
if (file.exists()) {
|
|
||||||
if (!force) {
|
|
||||||
System.out.println("Output file exists. Use the -f option to replace it.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//backup file to package directory
|
|
||||||
else {
|
|
||||||
action = "Update";
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info("Target Backup:"+targetDirectoryModified+sourceName);
|
|
||||||
source = OpenInputfile(targetDirectoryModified+sourceName);
|
|
||||||
SimpleDateFormat formatter_file = new SimpleDateFormat("yyMMddHHmmssSSSSZ");
|
|
||||||
Date today = new Date();
|
|
||||||
fileDate = formatter_file.format(today);
|
|
||||||
copy = OpenOutputfile(packagePath+File.separator+"backup"+File.separator+fileDate+"_"+sourceName);
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info("Source Backup:"+packagePath+File.separator+"backup"+File.separator+fileDate+"_"+sourceName);
|
|
||||||
copyFile (source,copy);
|
|
||||||
log.info("Backup Complete");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int success = readReplace(targetDirectoryModified+sourceName, oldCode, newCode);
|
|
||||||
|
|
||||||
X_AD_Package_Imp_Detail impDetail = createImportDetail(ctx, "codesnipit", sourceName, 0);
|
|
||||||
// Record in log
|
|
||||||
if (success != -1){
|
|
||||||
try {
|
|
||||||
logImportDetail (ctx, impDetail, 1, sourceName, 0, action);
|
|
||||||
} catch (SAXException e) {
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info ("setfile:"+e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
try {
|
|
||||||
logImportDetail (ctx, impDetail, 0, sourceName, 0, action);
|
|
||||||
} catch (SAXException e) {
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info ("setfile:"+e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Record in transaction file
|
|
||||||
X_AD_Package_Imp_Backup backup = new X_AD_Package_Imp_Backup(ctx.ctx, 0, getTrxName(ctx));
|
|
||||||
backup.setAD_Org_ID(Env.getAD_Org_ID(ctx.ctx));
|
|
||||||
backup.setAD_Package_Imp_ID(getPackageImpId(ctx.ctx));
|
|
||||||
backup.setAD_Package_Imp_Org_Dir(targetDirectoryModified+sourceName );
|
|
||||||
backup.setAD_Package_Imp_Bck_Dir(packagePath+File.separator+"backup"+File.separator+fileDate+"_"+sourceName);
|
|
||||||
backup.saveEx();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find and replace code
|
|
||||||
*
|
|
||||||
* @param file name
|
|
||||||
* @param old string
|
|
||||||
* @param new string
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static int readReplace(String fname, String oldPattern, String replPattern){
|
|
||||||
String line;
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
FileInputStream fis = new FileInputStream(fname);
|
|
||||||
BufferedReader reader=new BufferedReader ( new InputStreamReader(fis));
|
|
||||||
while((line = reader.readLine()) != null) {
|
|
||||||
line = line.replaceAll(oldPattern, replPattern);
|
|
||||||
System.err.println(line);
|
|
||||||
sb.append(line+"\n");
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
BufferedWriter out=new BufferedWriter ( new FileWriter(fname));
|
|
||||||
out.write(sb.toString());
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
catch (Throwable e) {
|
|
||||||
System.err.println("error replacing codesnipit "+e);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void endElement(PIPOContext ctx, Element element)
|
|
||||||
throws SAXException {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void create(PIPOContext pipoContext, TransformerHandler document)
|
|
||||||
throws SAXException {
|
|
||||||
String FileDir = Env.getContext(pipoContext.ctx, CodeSnippetElementParameters.DESTINATION_DIRECTORY);
|
|
||||||
String FileName = Env.getContext(pipoContext.ctx, CodeSnippetElementParameters.DESTINATION_FILE_NAME);
|
|
||||||
String OldCode = Env.getContext(pipoContext.ctx, CodeSnippetElementParameters.AD_Package_Code_Old);
|
|
||||||
String NewCode = Env.getContext(pipoContext.ctx, CodeSnippetElementParameters.AD_Package_Code_New);
|
|
||||||
String ReleaseNo = Env.getContext(pipoContext.ctx, CodeSnippetElementParameters.RELEASE_NO);
|
|
||||||
AttributesImpl atts = new AttributesImpl();
|
|
||||||
addTypeName(atts, "custom");
|
|
||||||
createSnipitBinding(atts, FileDir, FileName, OldCode, NewCode, ReleaseNo);
|
|
||||||
document.startElement("","","Code_Snipit",atts);
|
|
||||||
document.endElement("","","Code_Snipit");
|
|
||||||
}
|
|
||||||
|
|
||||||
private AttributesImpl createSnipitBinding( AttributesImpl atts, String FileDir, String FileName, String OldCode, String NewCode, String ReleaseNo)
|
|
||||||
{
|
|
||||||
atts.clear();
|
|
||||||
atts.addAttribute("","","filedir","CDATA",FileDir);
|
|
||||||
atts.addAttribute("","","filename","CDATA",FileName);
|
|
||||||
String preOldCode = OldCode.toString();
|
|
||||||
String preNewCode = NewCode.toString();
|
|
||||||
String modOldCode = preOldCode.replaceAll("\\$","\\\\\\$").replaceAll("\\.","\\\\.")
|
|
||||||
.replaceAll("\\^","\\\\^").replaceAll("\\(","\\\\(").replaceAll("\\)","\\\\)")
|
|
||||||
.replaceAll("\\[","\\\\[").replaceAll("\\/","\\\\/").replaceAll("\\+","\\\\+")
|
|
||||||
.replaceAll("\\*","\\\\*").replaceAll("\\|","\\\\|");
|
|
||||||
String modNewCode = preNewCode.replaceAll("\\$","\\\\\\$").replaceAll("\\.","\\\\.")
|
|
||||||
.replaceAll("\\^","\\\\^").replaceAll("\\(","\\\\(").replaceAll("\\)","\\\\)")
|
|
||||||
.replaceAll("\\[","\\\\[").replaceAll("\\/","\\\\/").replaceAll("\\+","\\\\+")
|
|
||||||
.replaceAll("\\*","\\\\*").replaceAll("\\|","\\\\|");
|
|
||||||
atts.addAttribute("","","oldcode","CDATA",modOldCode);
|
|
||||||
atts.addAttribute("","","newcode","CDATA",modNewCode);
|
|
||||||
atts.addAttribute("","","ReleaseNo","CDATA",ReleaseNo);
|
|
||||||
return atts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void packOut(PackOut packout, TransformerHandler packoutHandler, TransformerHandler docHandler,int recordId) throws Exception
|
|
||||||
{
|
|
||||||
PackoutItem detail = packout.getCurrentPackoutItem();
|
|
||||||
Env.setContext(packout.getCtx().ctx, CodeSnippetElementParameters.DESTINATION_DIRECTORY, (String)detail.getProperty(CodeSnippetElementParameters.DESTINATION_DIRECTORY));
|
|
||||||
Env.setContext(packout.getCtx().ctx, CodeSnippetElementParameters.DESTINATION_FILE_NAME, (String)detail.getProperty(CodeSnippetElementParameters.DESTINATION_FILE_NAME));
|
|
||||||
Env.setContext(packout.getCtx().ctx, CodeSnippetElementParameters.AD_Package_Code_Old, (String)detail.getProperty(CodeSnippetElementParameters.AD_Package_Code_Old));
|
|
||||||
Env.setContext(packout.getCtx().ctx, CodeSnippetElementParameters.AD_Package_Code_New, (String)detail.getProperty(CodeSnippetElementParameters.AD_Package_Code_New));
|
|
||||||
Env.setContext(packout.getCtx().ctx, CodeSnippetElementParameters.RELEASE_NO, (String)detail.getProperty(CodeSnippetElementParameters.RELEASE_NO));
|
|
||||||
this.create(packout.getCtx(), packoutHandler);
|
|
||||||
packout.getCtx().ctx.remove(CodeSnippetElementParameters.DESTINATION_DIRECTORY);
|
|
||||||
packout.getCtx().ctx.remove(CodeSnippetElementParameters.DESTINATION_FILE_NAME);
|
|
||||||
packout.getCtx().ctx.remove(CodeSnippetElementParameters.AD_Package_Code_Old);
|
|
||||||
packout.getCtx().ctx.remove(CodeSnippetElementParameters.AD_Package_Code_New);
|
|
||||||
packout.getCtx().ctx.remove(CodeSnippetElementParameters.RELEASE_NO);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,217 +0,0 @@
|
||||||
/******************************************************************************
|
|
||||||
* Product: Adempiere ERP & CRM Smart Business Solution *
|
|
||||||
* Copyright (C) 1999-2006 Adempiere, Inc. All Rights Reserved. *
|
|
||||||
* This program is free software; you can redistribute it and/or modify it *
|
|
||||||
* under the terms version 2 of the GNU General Public License as published *
|
|
||||||
* by the Free Software Foundation. This program is distributed in the hope *
|
|
||||||
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
||||||
* See the GNU General Public License for more details. *
|
|
||||||
* You should have received a copy of the GNU General Public License along *
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
|
||||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 Robert Klein. robeklein@hotmail.com
|
|
||||||
* Contributor(s): Low Heng Sin hengsin@avantz.com
|
|
||||||
*****************************************************************************/
|
|
||||||
package org.adempiere.pipo2.handler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import javax.xml.transform.sax.TransformerHandler;
|
|
||||||
|
|
||||||
import org.adempiere.pipo2.AbstractElementHandler;
|
|
||||||
import org.adempiere.pipo2.Element;
|
|
||||||
import org.adempiere.pipo2.PIPOContext;
|
|
||||||
import org.adempiere.pipo2.PackOut;
|
|
||||||
import org.adempiere.pipo2.PackoutItem;
|
|
||||||
import org.compiere.Adempiere;
|
|
||||||
import org.compiere.model.MPackageExp;
|
|
||||||
import org.compiere.model.X_AD_Package_Exp_Detail;
|
|
||||||
import org.compiere.model.X_AD_Package_Imp_Backup;
|
|
||||||
import org.compiere.model.X_AD_Package_Imp_Detail;
|
|
||||||
import org.compiere.util.Env;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import org.xml.sax.helpers.AttributesImpl;
|
|
||||||
|
|
||||||
public class DistFileElementHandler extends AbstractElementHandler {
|
|
||||||
|
|
||||||
String fileDest;
|
|
||||||
|
|
||||||
public DistFileElementHandler()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public DistFileElementHandler(String fileDest)
|
|
||||||
{
|
|
||||||
this.fileDest=fileDest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startElement(PIPOContext ctx, Element element) throws SAXException {
|
|
||||||
String action = null;
|
|
||||||
|
|
||||||
String releaseNumber = getStringValue(element,"ReleaseNo");
|
|
||||||
//Check Release Number
|
|
||||||
if(releaseNumber==null||Adempiere.MAIN_VERSION.equals(releaseNumber)||releaseNumber.equals("all")){
|
|
||||||
String fileName = getStringValue(element, "filename");
|
|
||||||
String sourceDirectory = getStringValue(element, "sourceDirectory");
|
|
||||||
String targetDirectory = getStringValue(element, "targetDirectory");
|
|
||||||
|
|
||||||
action = "New";
|
|
||||||
InputStream inputStream; // Stream for reading from the source file.
|
|
||||||
OutputStream outputStream; // Stream for writing the copy.
|
|
||||||
|
|
||||||
String packagePath=null;
|
|
||||||
String adempiereSourcePath=null;
|
|
||||||
|
|
||||||
//get adempiere-all directory
|
|
||||||
try {
|
|
||||||
packagePath = getPackageDirectory(ctx.ctx);
|
|
||||||
File parentDirectory = new File(packagePath);
|
|
||||||
while (!parentDirectory.getName().equals("packages")){
|
|
||||||
parentDirectory = parentDirectory.getParentFile();
|
|
||||||
}
|
|
||||||
parentDirectory = parentDirectory.getParentFile();
|
|
||||||
adempiereSourcePath = parentDirectory.getCanonicalPath();
|
|
||||||
} catch (IOException e1) {
|
|
||||||
System.out.println("Can't find adempiere-all directory.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Create backup directory if required
|
|
||||||
File backupDir = new File(packagePath+File.separator+"backup"+File.separator);
|
|
||||||
if (!backupDir.exists()){
|
|
||||||
boolean success = (new File(packagePath+File.separator+"backup"+File.separator)).mkdirs();
|
|
||||||
if (!success) {
|
|
||||||
log.info("Backup directory creation failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Correct target directory for proper file seperator
|
|
||||||
String fullTargetPath = adempiereSourcePath+targetDirectory;
|
|
||||||
char slash1 = '\\';
|
|
||||||
char slash2 = '/';
|
|
||||||
if (File.separator.equals("/"))
|
|
||||||
fullTargetPath = fullTargetPath.replace(slash1,slash2);
|
|
||||||
else
|
|
||||||
fullTargetPath = fullTargetPath.replace(slash2,slash1);
|
|
||||||
|
|
||||||
File file = new File(fullTargetPath+fileName);
|
|
||||||
//TODO: derive force from user parameter
|
|
||||||
boolean force = true;
|
|
||||||
String fileDate = null;
|
|
||||||
//check to see if overwrites are allowed
|
|
||||||
if (file.exists())
|
|
||||||
{
|
|
||||||
if (!force) {
|
|
||||||
System.out.println(
|
|
||||||
"Output file exists. Use the -f option to replace it.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//backup file to package directory
|
|
||||||
else {
|
|
||||||
action = "Update";
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info("Target Backup:"+fullTargetPath+fileName);
|
|
||||||
inputStream = OpenInputfile(fullTargetPath+fileName);
|
|
||||||
SimpleDateFormat formatter_file = new SimpleDateFormat("yyMMddHHmmssSSSSZ");
|
|
||||||
Date today = new Date();
|
|
||||||
fileDate = formatter_file.format(today);
|
|
||||||
outputStream = OpenOutputfile(packagePath+File.separator+"backup"+File.separator+fileDate+"_"+fileName);
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info("Source Backup:"+packagePath+File.separator+"backup"+File.separator+fileDate+"_"+fileName);
|
|
||||||
copyFile (inputStream, outputStream);
|
|
||||||
log.info("Backup Complete");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Correct dist directory for proper file seperator
|
|
||||||
String fullSourcePath=null;
|
|
||||||
if (File.separator.equals("/"))
|
|
||||||
fullSourcePath = sourceDirectory.replace(slash1,slash2);
|
|
||||||
else
|
|
||||||
fullSourcePath = sourceDirectory.replace(slash2,slash1);
|
|
||||||
inputStream = OpenInputfile(packagePath+fullSourcePath+fileName);
|
|
||||||
|
|
||||||
// Create Target directory if required
|
|
||||||
File targetDir = new File(fullTargetPath);
|
|
||||||
if (!targetDir.exists()){
|
|
||||||
boolean success = (new File(fullTargetPath)).mkdirs();
|
|
||||||
if (!success) {
|
|
||||||
log.info("Target directory creation failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outputStream = OpenOutputfile(fullTargetPath+fileName);
|
|
||||||
//Copy File
|
|
||||||
int success = copyFile (inputStream,outputStream);
|
|
||||||
//Record in log
|
|
||||||
X_AD_Package_Imp_Detail impDetail = createImportDetail(ctx, "file", fileName, 0);
|
|
||||||
if (success != -1){
|
|
||||||
try {
|
|
||||||
logImportDetail (ctx, impDetail, 1, fileName, 0, action);
|
|
||||||
} catch (SAXException e) {
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info ("setfile:"+e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
try {
|
|
||||||
logImportDetail (ctx, impDetail, 0, fileName, 0, action);
|
|
||||||
} catch (SAXException e) {
|
|
||||||
if (log.isLoggable(Level.INFO)) log.info ("setfile:"+e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Record in transaction file
|
|
||||||
X_AD_Package_Imp_Backup backup = new X_AD_Package_Imp_Backup(ctx.ctx, 0, getTrxName(ctx));
|
|
||||||
backup.setAD_Org_ID(Env.getAD_Org_ID(ctx.ctx));
|
|
||||||
backup.setAD_Package_Imp_Org_Dir(fullTargetPath+fileName);
|
|
||||||
backup.setAD_Package_Imp_Bck_Dir(packagePath+File.separator+"backup"+File.separator+fileDate+"_"+fileName);
|
|
||||||
backup.setAD_Package_Imp_ID(getPackageImpId(ctx.ctx));
|
|
||||||
backup.saveEx();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endElement(PIPOContext ctx, Element element) throws SAXException {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void create(PIPOContext ctx, TransformerHandler document)
|
|
||||||
throws SAXException {
|
|
||||||
String FileName = Env.getContext(ctx.ctx, X_AD_Package_Exp_Detail.COLUMNNAME_FileName);
|
|
||||||
String Source_Directory = Env.getContext(ctx.ctx, "Source_Directory");
|
|
||||||
String Target_Directory = Env.getContext(ctx.ctx, X_AD_Package_Exp_Detail.COLUMNNAME_Target_Directory);
|
|
||||||
String ReleaseNo = Env.getContext(ctx.ctx, X_AD_Package_Exp_Detail.COLUMNNAME_ReleaseNo);
|
|
||||||
AttributesImpl atts = new AttributesImpl();
|
|
||||||
addTypeName(atts, "custom");
|
|
||||||
document.startElement("","","Dist_File",atts);
|
|
||||||
addTextProperty(document,"filename",FileName);
|
|
||||||
addTextProperty(document,"sourceDirectory",Source_Directory);
|
|
||||||
addTextProperty(document,"targetDirectory",Target_Directory);
|
|
||||||
addTextProperty(document,"ReleaseNo",ReleaseNo);
|
|
||||||
atts.addAttribute("","","ReleaseNo","CDATA",ReleaseNo);
|
|
||||||
document.endElement("","","Dist_File");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doPackout(PackOut packout, MPackageExp header, PackoutItem detail,TransformerHandler packOutDocument,TransformerHandler packageDocument,AttributesImpl atts,int recordId) throws Exception
|
|
||||||
{
|
|
||||||
Env.setContext(packout.getCtx().ctx, X_AD_Package_Exp_Detail.COLUMNNAME_FileName, (String)detail.getProperty("FileName"));
|
|
||||||
Env.setContext(packout.getCtx().ctx, X_AD_Package_Exp_Detail.COLUMNNAME_ReleaseNo, (String)detail.getProperty("ReleaseNo"));
|
|
||||||
Env.setContext(packout.getCtx().ctx, X_AD_Package_Exp_Detail.COLUMNNAME_Target_Directory, (String)detail.getProperty("TargetDirectory"));
|
|
||||||
Env.setContext(packout.getCtx().ctx, "Source_Directory", fileDest);
|
|
||||||
this.create(packout.getCtx(), packOutDocument);
|
|
||||||
packout.getCtx().ctx.remove(X_AD_Package_Exp_Detail.COLUMNNAME_FileName);
|
|
||||||
packout.getCtx().ctx.remove(X_AD_Package_Exp_Detail.COLUMNNAME_ReleaseNo);
|
|
||||||
packout.getCtx().ctx.remove(X_AD_Package_Exp_Detail.COLUMNNAME_Target_Directory);
|
|
||||||
packout.getCtx().ctx.remove("Source_Directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void packOut(PackOut packout, TransformerHandler packoutHandler,
|
|
||||||
TransformerHandler docHandler,
|
|
||||||
int recordId) throws Exception {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package org.adempiere.pipo2.handler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import javax.xml.transform.sax.TransformerHandler;
|
|
||||||
|
|
||||||
import org.adempiere.exceptions.AdempiereException;
|
|
||||||
import org.adempiere.pipo2.Element;
|
|
||||||
import org.adempiere.pipo2.ElementHandler;
|
|
||||||
import org.adempiere.pipo2.FileElementParameters;
|
|
||||||
import org.adempiere.pipo2.PIPOContext;
|
|
||||||
import org.adempiere.pipo2.PackOut;
|
|
||||||
import org.adempiere.pipo2.PackoutDocument;
|
|
||||||
import org.adempiere.pipo2.PackoutItem;
|
|
||||||
import org.compiere.util.CLogger;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import org.xml.sax.helpers.AttributesImpl;
|
|
||||||
|
|
||||||
public class FileElementHandler implements ElementHandler {
|
|
||||||
|
|
||||||
private static final CLogger log = CLogger.getCLogger(FileElementHandler.class);
|
|
||||||
|
|
||||||
public void packOut(PackOut packout, TransformerHandler packoutHandler,
|
|
||||||
TransformerHandler docHandler, int recordId) throws Exception {
|
|
||||||
PackoutDocument header = packout.getPackoutDocument();
|
|
||||||
PackoutItem detail = packout.getCurrentPackoutItem();
|
|
||||||
if (log.isLoggable(Level.INFO))log.log(Level.INFO,
|
|
||||||
"In PackOut.java handling Code or Other 2pack module creation");
|
|
||||||
String fileDirectory = packout.getPackoutDirectory() + header.getPackageName()
|
|
||||||
+ detail.getProperty(FileElementParameters.TARGET_DIRECTORY);
|
|
||||||
if (log.isLoggable(Level.INFO))log.log(Level.INFO, "targetDirectory" + fileDirectory);
|
|
||||||
String targetDirectory = null;
|
|
||||||
char fileseperator1 = '/';
|
|
||||||
char fileseperator2 = '\\';
|
|
||||||
// Correct package for proper file seperator
|
|
||||||
if (File.separator.equals("/")) {
|
|
||||||
targetDirectory = fileDirectory.replace(fileseperator2,
|
|
||||||
fileseperator1);
|
|
||||||
} else
|
|
||||||
targetDirectory = fileDirectory.replace(fileseperator1,
|
|
||||||
fileseperator2);
|
|
||||||
|
|
||||||
String sourceDirectory = null;
|
|
||||||
fileDirectory = (String) detail.getProperty(FileElementParameters.SOURCE_DIRECTORY);
|
|
||||||
// Correct package for proper file seperator
|
|
||||||
if (File.separator.equals("/")) {
|
|
||||||
sourceDirectory = fileDirectory.replace(fileseperator2,
|
|
||||||
fileseperator1);
|
|
||||||
} else
|
|
||||||
sourceDirectory = fileDirectory.replace(fileseperator2,
|
|
||||||
fileseperator1);
|
|
||||||
|
|
||||||
packout.copyFile(sourceDirectory + detail.getProperty(FileElementParameters.FILE_NAME),
|
|
||||||
targetDirectory + detail.getProperty(FileElementParameters.FILE_NAME));
|
|
||||||
|
|
||||||
AttributesImpl atts = new AttributesImpl();
|
|
||||||
|
|
||||||
if (detail.getProperty(FileElementParameters.DESTINATION_DIRECTORY) != null) {
|
|
||||||
|
|
||||||
fileDirectory = (String) detail.getProperty(FileElementParameters.DESTINATION_DIRECTORY);
|
|
||||||
String destinationDirectory = null;
|
|
||||||
|
|
||||||
// Correct package for proper file seperator
|
|
||||||
if (File.separator.equals("/")) {
|
|
||||||
destinationDirectory = fileDirectory.replace(fileseperator2,
|
|
||||||
fileseperator1);
|
|
||||||
} else
|
|
||||||
destinationDirectory = fileDirectory.replace(fileseperator2,
|
|
||||||
fileseperator1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
new DistFileElementHandler(destinationDirectory).doPackout(
|
|
||||||
packout, null, detail, packoutHandler, null, null, 0);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new AdempiereException(e.getLocalizedMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detail.getProperty(FileElementParameters.FILE_NAME) != null) {
|
|
||||||
PackOut.addTextElement(docHandler, "file",
|
|
||||||
"File: " + detail.getProperty(FileElementParameters.FILE_NAME), atts);
|
|
||||||
}
|
|
||||||
PackOut.addTextElement(docHandler, "filedirectory", "Directory: "
|
|
||||||
+ detail.getProperty(FileElementParameters.TARGET_DIRECTORY), atts);
|
|
||||||
PackOut.addTextElement(docHandler, "filenotes",
|
|
||||||
"Notes: " + detail.getProperty(FileElementParameters.DESCRIPTION), atts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startElement(PIPOContext ctx, Element element)
|
|
||||||
throws SAXException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endElement(PIPOContext ctx, Element element) throws SAXException {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/******************************************************************************
|
|
||||||
* Product: Adempiere ERP & CRM Smart Business Solution *
|
|
||||||
* Copyright (C) 2010 Heng Sin Low *
|
|
||||||
* This program is free software; you can redistribute it and/or modify it *
|
|
||||||
* under the terms version 2 of the GNU General Public License as published *
|
|
||||||
* by the Free Software Foundation. This program is distributed in the hope *
|
|
||||||
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
||||||
* See the GNU General Public License for more details. *
|
|
||||||
* You should have received a copy of the GNU General Public License along *
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
|
||||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
|
||||||
*****************************************************************************/
|
|
||||||
package org.adempiere.pipo2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author hengsin
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface CodeSnippetElementParameters {
|
|
||||||
public final static String DESTINATION_DIRECTORY = "DestinationDirectory";
|
|
||||||
public final static String DESTINATION_FILE_NAME = "DestinationFileName";
|
|
||||||
public final static String AD_Package_Code_Old = "AD_Package_Code_Old";
|
|
||||||
public final static String AD_Package_Code_New = "AD_Package_Code_New";
|
|
||||||
public final static String RELEASE_NO = "ReleaseNo";
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/******************************************************************************
|
|
||||||
* Product: Adempiere ERP & CRM Smart Business Solution *
|
|
||||||
* Copyright (C) 2010 Heng Sin Low *
|
|
||||||
* This program is free software; you can redistribute it and/or modify it *
|
|
||||||
* under the terms version 2 of the GNU General Public License as published *
|
|
||||||
* by the Free Software Foundation. This program is distributed in the hope *
|
|
||||||
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
||||||
* See the GNU General Public License for more details. *
|
|
||||||
* You should have received a copy of the GNU General Public License along *
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc., *
|
|
||||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
|
||||||
*****************************************************************************/
|
|
||||||
package org.adempiere.pipo2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author hengsin
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface FileElementParameters {
|
|
||||||
public final static String TARGET_DIRECTORY = "TargetDirectory";
|
|
||||||
public final static String SOURCE_DIRECTORY = "SourceDirectory";
|
|
||||||
public final static String FILE_NAME = "FileName";
|
|
||||||
public final static String DESTINATION_DIRECTORY = "DestinationDirectory";
|
|
||||||
public final static String DESCRIPTION = "Description";
|
|
||||||
public final static String RELEASE_NO = "ReleaseNo";
|
|
||||||
}
|
|
|
@ -158,16 +158,12 @@ public class PackOutProcess extends SvrProcess
|
||||||
private String getTypeName(String type) {
|
private String getTypeName(String type) {
|
||||||
if (X_AD_Package_Exp_Detail.TYPE_ApplicationOrModule.equals(type))
|
if (X_AD_Package_Exp_Detail.TYPE_ApplicationOrModule.equals(type))
|
||||||
return I_AD_Menu.Table_Name;
|
return I_AD_Menu.Table_Name;
|
||||||
else if (X_AD_Package_Exp_Detail.TYPE_CodeSnipit.equals(type))
|
|
||||||
return "Code_Snipit";
|
|
||||||
else if (X_AD_Package_Exp_Detail.TYPE_Data.equals(type))
|
else if (X_AD_Package_Exp_Detail.TYPE_Data.equals(type))
|
||||||
return IHandlerRegistry.TABLE_GENERIC_HANDLER;
|
return IHandlerRegistry.TABLE_GENERIC_HANDLER;
|
||||||
else if (X_AD_Package_Exp_Detail.TYPE_DataSingle.equals(type))
|
else if (X_AD_Package_Exp_Detail.TYPE_DataSingle.equals(type))
|
||||||
return IHandlerRegistry.TABLE_GENERIC_SINGLE_HANDLER;
|
return IHandlerRegistry.TABLE_GENERIC_SINGLE_HANDLER;
|
||||||
else if (X_AD_Package_Exp_Detail.TYPE_DynamicValidationRule.equals(type))
|
else if (X_AD_Package_Exp_Detail.TYPE_DynamicValidationRule.equals(type))
|
||||||
return I_AD_Val_Rule.Table_Name;
|
return I_AD_Val_Rule.Table_Name;
|
||||||
else if (X_AD_Package_Exp_Detail.TYPE_File_CodeOrOther.equals(type))
|
|
||||||
return "Dist_File";
|
|
||||||
else if (X_AD_Package_Exp_Detail.TYPE_Form.equals(type))
|
else if (X_AD_Package_Exp_Detail.TYPE_Form.equals(type))
|
||||||
return I_AD_Form.Table_Name;
|
return I_AD_Form.Table_Name;
|
||||||
else if (X_AD_Package_Exp_Detail.TYPE_ImportFormat.equals(type))
|
else if (X_AD_Package_Exp_Detail.TYPE_ImportFormat.equals(type))
|
||||||
|
@ -212,19 +208,6 @@ public class PackOutProcess extends SvrProcess
|
||||||
} else if (MPackageExpDetail.TYPE_SQLStatement.equals(type) || MPackageExpDetail.TYPE_SQLMandatory.equals(type)) {
|
} else if (MPackageExpDetail.TYPE_SQLStatement.equals(type) || MPackageExpDetail.TYPE_SQLMandatory.equals(type)) {
|
||||||
properties.put(SQLElementParameters.SQL_STATEMENT, dtl.getSQLStatement());
|
properties.put(SQLElementParameters.SQL_STATEMENT, dtl.getSQLStatement());
|
||||||
properties.put(SQLElementParameters.DB_TYPE, dtl.getDBType());
|
properties.put(SQLElementParameters.DB_TYPE, dtl.getDBType());
|
||||||
} else if (MPackageExpDetail.TYPE_File_CodeOrOther.equals(type)) {
|
|
||||||
properties.put(FileElementParameters.TARGET_DIRECTORY, dtl.getTarget_Directory());
|
|
||||||
properties.put(FileElementParameters.SOURCE_DIRECTORY, dtl.getFile_Directory());
|
|
||||||
properties.put(FileElementParameters.FILE_NAME, dtl.getFileName());
|
|
||||||
properties.put(FileElementParameters.DESTINATION_DIRECTORY, dtl.getDestination_Directory());
|
|
||||||
properties.put(FileElementParameters.DESCRIPTION, dtl.getDescription());
|
|
||||||
properties.put(FileElementParameters.RELEASE_NO, dtl.getReleaseNo());
|
|
||||||
} else if (MPackageExpDetail.TYPE_CodeSnipit.equals(type)) {
|
|
||||||
properties.put(CodeSnippetElementParameters.DESTINATION_DIRECTORY, dtl.getDestination_Directory());
|
|
||||||
properties.put(CodeSnippetElementParameters.DESTINATION_FILE_NAME, dtl.getDestination_FileName());
|
|
||||||
properties.put(CodeSnippetElementParameters.RELEASE_NO, dtl.getReleaseNo());
|
|
||||||
properties.put(CodeSnippetElementParameters.AD_Package_Code_Old, dtl.getAD_Package_Code_Old());
|
|
||||||
properties.put(CodeSnippetElementParameters.AD_Package_Code_New, dtl.getAD_Package_Code_New());
|
|
||||||
}
|
}
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue