- fix wrong cast to Spatial in AssetDataObject
- cleanup and unify opening/closing of AssetData objects
- add logging for opening/closing of AssetData
- close associated assets when closing models (e.g. textures, materials etc.)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10310 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
nor..67 12 years ago
parent fdebe024c6
commit 15174582de
  1. 18
      sdk/jme3-blender/src/com/jme3/gde/blender/filetypes/AbstractBlenderImportDataObject.java
  2. 14
      sdk/jme3-core/src/com/jme3/gde/core/assets/AssetDataObject.java
  3. 4
      sdk/jme3-core/src/com/jme3/gde/core/assets/ExternalChangeScanner.java
  4. 6
      sdk/jme3-core/src/com/jme3/gde/core/assets/SpatialAssetDataObject.java
  5. 3
      sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/Bundle.properties
  6. 13
      sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel4.form
  7. 11
      sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel4.java
  8. 16
      sdk/jme3-ogretools/src/com/jme3/gde/ogretools/OgreBinaryMeshDataObject.java

@ -47,8 +47,13 @@ public abstract class AbstractBlenderImportDataObject extends SpatialAssetDataOb
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager.")); DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager."));
return null; return null;
} }
//make sure its actually closed and all data gets reloaded
closeAsset();
FileObject mainFile = getPrimaryFile(); FileObject mainFile = getPrimaryFile();
BlenderTool.runConversionScript(SUFFIX, mainFile); if (!BlenderTool.runConversionScript(SUFFIX, mainFile)) {
logger.log(Level.SEVERE, "Failed to create model, running blender caused an error");
return null;
}
mainFile.getParent().refresh(); mainFile.getParent().refresh();
FileObject outFile = FileUtil.findBrother(mainFile, BlenderTool.TEMP_SUFFIX); FileObject outFile = FileUtil.findBrother(mainFile, BlenderTool.TEMP_SUFFIX);
if (outFile == null) { if (outFile == null) {
@ -76,8 +81,9 @@ public abstract class AbstractBlenderImportDataObject extends SpatialAssetDataOb
Spatial spatial = mgr.loadModel(key); Spatial spatial = mgr.loadModel(key);
replaceFiles(); replaceFiles();
listListener.stop(); listListener.stop();
savable = spatial;
SpatialUtil.storeOriginalPathUserData(spatial); SpatialUtil.storeOriginalPathUserData(spatial);
savable = spatial;
logger.log(Level.INFO, "Loaded asset {0}", getName());
return spatial; return spatial;
} catch (IOException ex) { } catch (IOException ex) {
Exceptions.printStackTrace(ex); Exceptions.printStackTrace(ex);
@ -96,13 +102,13 @@ public abstract class AbstractBlenderImportDataObject extends SpatialAssetDataOb
@Override @Override
public synchronized BlenderKey getAssetKey() { public synchronized BlenderKey getAssetKey() {
if(super.getAssetKey() instanceof BlenderKey){ if (super.getAssetKey() instanceof BlenderKey) {
return (BlenderKey)assetKey; return (BlenderKey) assetKey;
} }
assetKey = new BlenderKey(super.getAssetKey().getName()); assetKey = new BlenderKey(super.getAssetKey().getName());
return (BlenderKey)assetKey; return (BlenderKey) assetKey;
} }
protected void replaceFiles() { protected void replaceFiles() {
for (int i = 0; i < assetList.size(); i++) { for (int i = 0; i < assetList.size(); i++) {
FileObject fileObject = assetList.get(i); FileObject fileObject = assetList.get(i);

@ -179,21 +179,24 @@ public class AssetDataObject extends MultiDataObject {
*/ */
public synchronized Savable loadAsset() { public synchronized Savable loadAsset() {
if (savable != null) { if (savable != null) {
return (Spatial) savable; return savable;
} }
ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class); ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
if (mgr == null) { if (mgr == null) {
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager.")); DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager."));
return null; return null;
} }
//make sure its actually closed and all data gets reloaded
closeAsset();
FileLock lock = null; FileLock lock = null;
try { try {
lock = getPrimaryFile().lock(); lock = getPrimaryFile().lock();
listListener.start(); listListener.start();
Savable spatial = (Savable) mgr.loadAsset(getAssetKey()); Savable spatial = (Savable) mgr.loadAsset(getAssetKey());
listListener.stop(); listListener.stop();
savable = spatial;
lock.releaseLock(); lock.releaseLock();
savable = spatial;
logger.log(Level.INFO, "Loaded asset {0}", getName());
} catch (Exception ex) { } catch (Exception ex) {
Exceptions.printStackTrace(ex); Exceptions.printStackTrace(ex);
} finally { } finally {
@ -251,18 +254,19 @@ public class AssetDataObject extends MultiDataObject {
*/ */
public synchronized void closeAsset() { public synchronized void closeAsset() {
ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class); ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
if (mgr != null) { if (mgr != null && savable != null) {
logger.log(Level.INFO, "Closing asset {0}, deleting from cache.", getName()); logger.log(Level.INFO, "Closing asset {0}, deleting from cache.", getName());
mgr.deleteFromCache(getAssetKey()); mgr.deleteFromCache(getAssetKey());
//delete referenced assets too //delete referenced assets too
for (Iterator<AssetKey> it = assetKeyList.iterator(); it.hasNext();) { for (Iterator<AssetKey> it = assetKeyList.iterator(); it.hasNext();) {
AssetKey assetKey1 = it.next(); AssetKey assetKey1 = it.next();
logger.log(Level.INFO, "Removing linked asset {0}, from cache via main asset {1}.", new Object[]{assetKey1.getName(), getName()});
mgr.deleteFromCache(assetKey1); mgr.deleteFromCache(assetKey1);
} }
} else { savable = null;
} else if(mgr == null){
logger.log(Level.WARNING, "Closing asset {0} with no ProjectAssetManager assigned..?", getName()); logger.log(Level.WARNING, "Closing asset {0} with no ProjectAssetManager assigned..?", getName());
} }
savable = null;
} }
/** /**

@ -35,8 +35,6 @@ import com.jme3.export.Savable;
import com.jme3.gde.core.scene.ApplicationLogHandler; import com.jme3.gde.core.scene.ApplicationLogHandler;
import com.jme3.gde.core.scene.SceneApplication; import com.jme3.gde.core.scene.SceneApplication;
import com.jme3.gde.core.util.SpatialUtil; import com.jme3.gde.core.util.SpatialUtil;
import com.jme3.scene.Geometry;
import com.jme3.scene.SceneGraphVisitorAdapter;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -164,7 +162,7 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener, F
logger.log(Level.INFO, "{0} listening for external changes on {1}", new Object[]{assetDataObject.getName(), fileObject}); logger.log(Level.INFO, "{0} listening for external changes on {1}", new Object[]{assetDataObject.getName(), fileObject});
originalObject = fileObject; originalObject = fileObject;
} else { } else {
logger.log(Level.INFO, "Ignoring old reference to self for {0}", assetDataObject.getName()); logger.log(Level.FINE, "Ignoring old reference to self for {0}", assetDataObject.getName());
} }
} else { } else {
logger.log(Level.INFO, "Could not get FileObject for file when trying to find original model file. Possibly deleted."); logger.log(Level.INFO, "Could not get FileObject for file when trying to find original model file. Possibly deleted.");

@ -87,18 +87,20 @@ public class SpatialAssetDataObject extends AssetDataObject {
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager.")); DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager."));
return null; return null;
} }
//make sure its actually closed and all data gets reloaded
closeAsset();
FileLock lock = null; FileLock lock = null;
try { try {
lock = getPrimaryFile().lock(); lock = getPrimaryFile().lock();
mgr.deleteFromCache(getAssetKey());
listListener.start(); listListener.start();
Spatial spatial = mgr.loadModel(getAssetKey()); Spatial spatial = mgr.loadModel(getAssetKey());
listListener.stop(); listListener.stop();
savable = spatial;
if (!(this instanceof BinaryModelDataObject)) { if (!(this instanceof BinaryModelDataObject)) {
SpatialUtil.storeOriginalPathUserData(spatial); SpatialUtil.storeOriginalPathUserData(spatial);
} }
lock.releaseLock(); lock.releaseLock();
savable = spatial;
logger.log(Level.INFO, "Loaded asset {0}", getName());
return spatial; return spatial;
} catch (Exception ex) { } catch (Exception ex) {
Exceptions.printStackTrace(ex); Exceptions.printStackTrace(ex);

@ -15,7 +15,7 @@ ModelImporterVisualPanel3.jCheckBox1.text=copy original model file(s) to project
ModelImporterVisualPanel4.jLabel1.text=Import to path: ModelImporterVisualPanel4.jLabel1.text=Import to path:
ModelImporterVisualPanel4.jTextField1.text=/Models/MyModel ModelImporterVisualPanel4.jTextField1.text=/Models/MyModel
ModelImporterVisualPanel4.jCheckBox1.text=copy original model file(s) to project folder ModelImporterVisualPanel4.jCheckBox1.text=copy original model file(s) to project folder
ModelImporterVisualPanel4.jTextArea1.text=The model will be converted to j3o binary format and copied \nto the project folder including associated texture etc. files.\nThe given path will be used as the root folder for the model.\n\nNote that the texture paths stored in the j3o file will be absolute! To have e.g. your textures in the Textures folder, import the model to the Textures folder and then move the j3o to the Models folder after the import.\n\nIf you copy the original model files to the project folder you can re-convert the model at any time by double-clicking it. ModelImporterVisualPanel4.jTextArea1.text=The model will be converted to j3o binary format and copied \nto the project folder including associated texture etc. files.\nThe given path will be used as the root folder for the model.\n\nNote that the texture paths stored in the j3o file will be absolute! To have e.g. your textures in the Textures folder, import the model to the Textures folder and then move the j3o to the Models folder after the import.\n\nIf you copy the original model files to the project folder the SDK will attempt to track changes in the original file and apply them to the j3o.
ModelImporterVisualPanel3.jButton3.text=< ModelImporterVisualPanel3.jButton3.text=<
ModelImporterVisualPanel3.jButton4.text=> ModelImporterVisualPanel3.jButton4.text=>
ModelImporterVisualPanel3.jButton2.text=- ModelImporterVisualPanel3.jButton2.text=-
@ -38,3 +38,4 @@ ModelImporterVisualPanel3.infoTextArea.text_1=Import Status Help
ModelImporterVisualPanel3.infoTextArea.text_good=Check if the model looks as expected.\nWith the buttons above you can move the camera. ModelImporterVisualPanel3.infoTextArea.text_good=Check if the model looks as expected.\nWith the buttons above you can move the camera.
ModelImporterVisualPanel3.infoTextArea.text_missing=Check if the model looks as expected, some textures fail to load. Textures that can not be found will be replaced with a red material.\nYou can also apply textures later by copying them to the project folder and applying them via a j3m material.\nThe UV coords are saved in the mesh so the mapping data will still be available.\nWith the buttons above you can move the camera. ModelImporterVisualPanel3.infoTextArea.text_missing=Check if the model looks as expected, some textures fail to load. Textures that can not be found will be replaced with a red material.\nYou can also apply textures later by copying them to the project folder and applying them via a j3m material.\nThe UV coords are saved in the mesh so the mapping data will still be available.\nWith the buttons above you can move the camera.
ModelImporterVisualPanel3.infoTextArea.text_failed=This file can not be loaded, it is either in an unsupported format or is incompatible.\nCheck the bottom right corner of the SDK for a little warning sign. If it is there double-click it and report the contained stack trace at jmonkeyengine.org. ModelImporterVisualPanel3.infoTextArea.text_failed=This file can not be loaded, it is either in an unsupported format or is incompatible.\nCheck the bottom right corner of the SDK for a little warning sign. If it is there double-click it and report the contained stack trace at jmonkeyengine.org.
ModelImporterVisualPanel4.jCheckBox1.toolTipText=If you copy the original model files to the project folder the SDK will attempt to track changes in the original file and apply them to the j3o.

@ -18,10 +18,10 @@
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0"> <Group type="102" alignment="0" attributes="0">
<Component id="jLabel1" min="-2" max="-2" attributes="0"/> <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="303" max="32767" attributes="0"/> <EmptySpace pref="322" max="32767" attributes="0"/>
</Group> </Group>
<Component id="jTextField1" alignment="0" pref="398" max="32767" attributes="0"/> <Component id="jTextField1" alignment="0" pref="417" max="32767" attributes="0"/>
<Component id="jCheckBox1" alignment="0" pref="398" max="32767" attributes="0"/> <Component id="jCheckBox1" alignment="0" pref="417" max="32767" attributes="0"/>
<Component id="jScrollPane1" alignment="1" max="32767" attributes="0"/> <Component id="jScrollPane1" alignment="1" max="32767" attributes="0"/>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -34,7 +34,7 @@
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="jCheckBox1" min="-2" max="-2" attributes="0"/> <Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="jScrollPane1" pref="192" max="32767" attributes="0"/> <Component id="jScrollPane1" pref="208" max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -59,6 +59,9 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/jme3/gde/modelimporter/Bundle.properties" key="ModelImporterVisualPanel4.jCheckBox1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="com/jme3/gde/modelimporter/Bundle.properties" key="ModelImporterVisualPanel4.jCheckBox1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/jme3/gde/modelimporter/Bundle.properties" key="ModelImporterVisualPanel4.jCheckBox1.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties> </Properties>
</Component> </Component>
<Container class="javax.swing.JScrollPane" name="jScrollPane1"> <Container class="javax.swing.JScrollPane" name="jScrollPane1">
@ -70,8 +73,8 @@
<SubComponents> <SubComponents>
<Component class="javax.swing.JTextArea" name="jTextArea1"> <Component class="javax.swing.JTextArea" name="jTextArea1">
<Properties> <Properties>
<Property name="columns" type="int" value="20"/>
<Property name="editable" type="boolean" value="false"/> <Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="20"/>
<Property name="lineWrap" type="boolean" value="true"/> <Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="5"/> <Property name="rows" type="int" value="5"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">

@ -52,9 +52,10 @@ public final class ModelImporterVisualPanel4 extends JPanel {
jTextField1.setText(org.openide.util.NbBundle.getMessage(ModelImporterVisualPanel4.class, "ModelImporterVisualPanel4.jTextField1.text")); // NOI18N jTextField1.setText(org.openide.util.NbBundle.getMessage(ModelImporterVisualPanel4.class, "ModelImporterVisualPanel4.jTextField1.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jCheckBox1, org.openide.util.NbBundle.getMessage(ModelImporterVisualPanel4.class, "ModelImporterVisualPanel4.jCheckBox1.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(jCheckBox1, org.openide.util.NbBundle.getMessage(ModelImporterVisualPanel4.class, "ModelImporterVisualPanel4.jCheckBox1.text")); // NOI18N
jCheckBox1.setToolTipText(org.openide.util.NbBundle.getMessage(ModelImporterVisualPanel4.class, "ModelImporterVisualPanel4.jCheckBox1.toolTipText")); // NOI18N
jTextArea1.setColumns(20);
jTextArea1.setEditable(false); jTextArea1.setEditable(false);
jTextArea1.setColumns(20);
jTextArea1.setLineWrap(true); jTextArea1.setLineWrap(true);
jTextArea1.setRows(5); jTextArea1.setRows(5);
jTextArea1.setText(org.openide.util.NbBundle.getMessage(ModelImporterVisualPanel4.class, "ModelImporterVisualPanel4.jTextArea1.text")); // NOI18N jTextArea1.setText(org.openide.util.NbBundle.getMessage(ModelImporterVisualPanel4.class, "ModelImporterVisualPanel4.jTextArea1.text")); // NOI18N
@ -68,9 +69,9 @@ public final class ModelImporterVisualPanel4 extends JPanel {
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addComponent(jLabel1) .addComponent(jLabel1)
.addContainerGap(303, Short.MAX_VALUE)) .addContainerGap(322, Short.MAX_VALUE))
.addComponent(jTextField1, javax.swing.GroupLayout.DEFAULT_SIZE, 398, Short.MAX_VALUE) .addComponent(jTextField1, javax.swing.GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE)
.addComponent(jCheckBox1, javax.swing.GroupLayout.DEFAULT_SIZE, 398, Short.MAX_VALUE) .addComponent(jCheckBox1, javax.swing.GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE)
.addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING)
); );
layout.setVerticalGroup( layout.setVerticalGroup(
@ -82,7 +83,7 @@ public final class ModelImporterVisualPanel4 extends JPanel {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jCheckBox1) .addComponent(jCheckBox1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE)) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 208, Short.MAX_VALUE))
); );
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables

@ -13,6 +13,7 @@ import com.jme3.gde.ogretools.convert.OgreXMLConvertOptions;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.DialogDisplayer; import org.openide.DialogDisplayer;
@ -34,6 +35,13 @@ public class OgreBinaryMeshDataObject extends SpatialAssetDataObject {
if (savable != null) { if (savable != null) {
return (Spatial) savable; return (Spatial) savable;
} }
ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
if (mgr == null) {
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager."));
return null;
}
//make sure its actually closed and all data gets reloaded
closeAsset();
ProgressHandle handle = ProgressHandleFactory.createHandle("Converting OgreBinary"); ProgressHandle handle = ProgressHandleFactory.createHandle("Converting OgreBinary");
handle.start(); handle.start();
//mesh //mesh
@ -49,11 +57,6 @@ public class OgreBinaryMeshDataObject extends SpatialAssetDataObject {
conv2.doConvert(options2, handle); conv2.doConvert(options2, handle);
} }
handle.progress("Convert Model"); handle.progress("Convert Model");
ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
if (mgr == null) {
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("File is not part of a project!\nCannot load without ProjectAssetManager."));
return null;
}
String assetKey = mgr.getRelativeAssetPath(options.getDestFile()); String assetKey = mgr.getRelativeAssetPath(options.getDestFile());
FileLock lock = null; FileLock lock = null;
try { try {
@ -63,12 +66,13 @@ public class OgreBinaryMeshDataObject extends SpatialAssetDataObject {
//replace transient xml files in list of assets for this model //replace transient xml files in list of assets for this model
replaceXmlFiles(mgr); replaceXmlFiles(mgr);
listListener.stop(); listListener.stop();
savable = spatial;
SpatialUtil.storeOriginalPathUserData(spatial); SpatialUtil.storeOriginalPathUserData(spatial);
lock.releaseLock(); lock.releaseLock();
File deleteFile = new File(options.getDestFile()); File deleteFile = new File(options.getDestFile());
deleteFile.delete(); deleteFile.delete();
handle.finish(); handle.finish();
savable = spatial;
logger.log(Level.INFO, "Loaded asset {0}", getName());
return spatial; return spatial;
} catch (IOException ex) { } catch (IOException ex) {
Exceptions.printStackTrace(ex); Exceptions.printStackTrace(ex);

Loading…
Cancel
Save