Commit 537fbff6 authored by rahadi's avatar rahadi

WIP: added form type xml parser

parent 75bfcb78
......@@ -597,85 +597,6 @@ public class CapiFormFragment extends Fragment implements FormListDownloaderList
Set<FormDetails> keys = result.keySet();
//RAHADI: Comments to resolve dependencies
// StringBuilder b = new StringBuilder();
// for (FormDetails k : keys) {
// b.append(k.formName + " (" + ((k.formVersion != null) ? (this.getString(R.string.version) + ": " + k.formVersion + " ") : "") + "ID: " + k.formID + ") - " + result.get(k));
// b.append("\n\n");
//
// //RAHADI
// if (result.get(k).equalsIgnoreCase(Collect.getInstance().getString(R.string.success))) {
// Cursor c = Collect.getInstance().getContentResolver().query(FormsProviderAPI.FormsColumns.CONTENT_URI, new String[]{FormsProviderAPI.FormsColumns.FORM_FILE_PATH}, FormsProviderAPI.FormsColumns.JR_FORM_ID + "=?", new String[]{k.formID}, null);
// if (c != null && c.moveToFirst() && c.getCount() == 1) {
// String formPath = c.getString(c.getColumnIndex(FormsProviderAPI.FormsColumns.FORM_FILE_PATH));
// c.close();
// File file = new File(formPath);
// if (file.exists()) {
// try {
// FileInputStream fis = new FileInputStream(file);
// KXmlParser parser = new KXmlParser();
// parser.setInput(new InputStreamReader(fis));
// Document doc = new Document();
// doc.parse(parser);
//
// Element root = doc.getRootElement();
// HashMap<String, String> namespaceSet = new HashMap<>();
// for (int i = 0; i < root.getAttributeCount(); i++) {
// if (root.getAttributeName(i).contains("xmlns")) {
// namespaceSet.put(root.getAttributeName(i), root.getAttributeValue(i));
// }
// }
// Element head = root.getElement(root.getNamespace(), "h:head");
// Element model = getChildElement(head, "model");
// Element instance = getChildElement(model, "instance");
//
// int i;
// int idx = instance.getChildCount();
// for (i = 0; i < idx; i++) {
// if (instance.isText(i)) {
// continue;
// }
//
// if (instance.getType(i) == Element.ELEMENT) {
// break;
// }
// }
//
// if (i < idx) {
// Element e = instance.getElement(i);
//
// File out = new File(Collect.EMPTY_INSTANCES_PATH + File.separator + k.formName + ".xml");
// int j = 2;
// while (out.exists()) {
// out = new File(Collect.EMPTY_INSTANCES_PATH + File.separator + k.formName + "_" + j + ".xml");
// j++;
// }
// FileOutputStream fos = new FileOutputStream(out);
// KXmlSerializer serializer = new KXmlSerializer();
// serializer.setOutput(new OutputStreamWriter(fos));
// Set<String> nsKeys = namespaceSet.keySet();
// for (String key : nsKeys) {
// e.setAttribute(root.getNamespace(), key, namespaceSet.get(key));
// }
// e.write(serializer);
// serializer.flush();
// serializer.endDocument();
// fos.close();
//
// ContentValues v = new ContentValues();
// v.put(FormsProviderAPI.FormsColumns.EMPTY_INSTANCE_FILE_PATH, out.getAbsolutePath());
// Collect.getInstance().getContentResolver().update(FormsProviderAPI.FormsColumns.CONTENT_URI, v, FormsProviderAPI.FormsColumns.JR_FORM_ID + "=?", new String[]{k.formID});
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// new GetGenerateTask(k.formID).execute(null);
// }
//
// }
// }
List<FormDetails> toDependenciesResolve = new ArrayList<>();
for (FormDetails k : keys) {
if (result.get(k).equalsIgnoreCase(Collect.getInstance().getString(R.string.success))) {
......
package id.ac.stis.capi.lessthink.utils;
import android.content.ContentValues;
import android.database.Cursor;
import org.javarosa.xform.parse.XFormParser;
import org.kxml2.io.KXmlParser;
import org.kxml2.io.KXmlSerializer;
import org.kxml2.kdom.Document;
import org.kxml2.kdom.Element;
import org.kxml2.kdom.Node;
......@@ -8,11 +13,14 @@ import org.kxml2.kdom.Node;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
......@@ -27,6 +35,10 @@ import javax.xml.xpath.XPathFactory;
import id.ac.stis.capi.lessthink.models.FormLabels;
import id.ac.stis.capi.lessthink.models.InstanceValues;
import id.ac.stis.capi.odk.application.Collect;
import id.ac.stis.capi.odk.dao.FormsDao;
import id.ac.stis.capi.odk.logic.FormDetails;
import id.ac.stis.capi.odk.provider.FormsProviderAPI;
import timber.log.Timber;
/**
......@@ -231,4 +243,82 @@ public class XmlUtils {
return null;
}
public static File generateEmptyInstanceFile(String formId, String formName) {
Cursor c = Collect.getInstance().getContentResolver().query(FormsProviderAPI.FormsColumns.CONTENT_URI,
new String[]{FormsProviderAPI.FormsColumns.FORM_FILE_PATH},
FormsProviderAPI.FormsColumns.JR_FORM_ID + "=?", new String[]{formId}, null);
if (c != null) {
if (c.moveToFirst() && c.getCount() == 1) {
String formPath = c.getString(c.getColumnIndex(FormsProviderAPI.FormsColumns.FORM_FILE_PATH));
File file = new File(formPath);
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
KXmlParser parser = new KXmlParser();
parser.setInput(new InputStreamReader(fis));
Document doc = new Document();
doc.parse(parser);
Element root = doc.getRootElement();
HashMap<String, String> namespaceSet = new HashMap<>();
for (int i = 0; i < root.getAttributeCount(); i++) {
if (root.getAttributeName(i).contains("xmlns")) {
namespaceSet.put(root.getAttributeName(i), root.getAttributeValue(i));
}
}
Element head = root.getElement(root.getNamespace(), "h:head");
Element model = getChildElement(head, "model");
Element instance = getChildElement(model, "instance");
int i;
int idx = instance.getChildCount();
for (i = 0; i < idx; i++) {
if (instance.isText(i)) {
continue;
}
if (instance.getType(i) == Element.ELEMENT) {
break;
}
}
if (i < idx) {
Element e = instance.getElement(i);
File out = new File(Collect.EMPTY_INSTANCES_PATH + File.separator + formName + ".xml");
int j = 2;
while (out.exists()) {
out = new File(Collect.EMPTY_INSTANCES_PATH + File.separator + formName + "_" + j + ".xml");
j++;
}
FileOutputStream fos = new FileOutputStream(out);
KXmlSerializer serializer = new KXmlSerializer();
serializer.setOutput(new OutputStreamWriter(fos));
Set<String> nsKeys = namespaceSet.keySet();
for (String key : nsKeys) {
e.setAttribute(root.getNamespace(), key, namespaceSet.get(key));
}
e.write(serializer);
serializer.flush();
serializer.endDocument();
fos.close();
ContentValues v = new ContentValues();
v.put(FormsProviderAPI.FormsColumns.EMPTY_INSTANCE_FILE_PATH, out.getAbsolutePath());
new FormsDao().updateForm(v, FormsProviderAPI.FormsColumns.JR_FORM_ID + "=?", new String[]{formId});
return out;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
c.close();
}
return null;
}
}
......@@ -20,13 +20,13 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import java.util.ArrayList;
import java.util.List;
import id.ac.stis.capi.odk.application.Collect;
import id.ac.stis.capi.odk.dto.Form;
import id.ac.stis.capi.odk.provider.FormsProviderAPI;
import java.util.ArrayList;
import java.util.List;
/**
* This class is used to encapsulate all access to the {@link id.ac.stis.capi.odk.provider.FormsProvider#DATABASE_NAME}
* For more information about this pattern go to https://en.wikipedia.org/wiki/Data_access_object
......@@ -128,6 +128,10 @@ public class FormsDao {
int formMediaPathColumnIndex = cursor.getColumnIndex(FormsProviderAPI.FormsColumns.FORM_MEDIA_PATH);
int languageColumnIndex = cursor.getColumnIndex(FormsProviderAPI.FormsColumns.LANGUAGE);
//RAHADI
int emptyInstanceFilePath = cursor.getColumnIndex(FormsProviderAPI.FormsColumns.EMPTY_INSTANCE_FILE_PATH);
int formType = cursor.getColumnIndex(FormsProviderAPI.FormsColumns.FORM_TYPE);
Form form = new Form.Builder()
.displayName(cursor.getString(displayNameColumnIndex))
.description(cursor.getString(descriptionColumnIndex))
......@@ -142,6 +146,8 @@ public class FormsDao {
.jrCacheFilePath(cursor.getString(jrCacheFilePathColumnIndex))
.formMediaPath(cursor.getString(formMediaPathColumnIndex))
.language(cursor.getString(languageColumnIndex))
.emptyInstanceFilePath(cursor.getString(emptyInstanceFilePath))
.formType(cursor.getString(formType))
.build();
forms.add(form);
......@@ -168,6 +174,11 @@ public class FormsDao {
values.put(FormsProviderAPI.FormsColumns.JRCACHE_FILE_PATH, form.getJrCacheFilePath());
values.put(FormsProviderAPI.FormsColumns.FORM_MEDIA_PATH, form.getFormMediaPath());
values.put(FormsProviderAPI.FormsColumns.LANGUAGE, form.getLanguage());
//RAHADI
values.put(FormsProviderAPI.FormsColumns.EMPTY_INSTANCE_FILE_PATH, form.getEmptyInstanceFilePath());
values.put(FormsProviderAPI.FormsColumns.FORM_TYPE, form.getFormType());
return values;
}
}
......@@ -36,6 +36,8 @@ public class Form {
private String jrCacheFilePath;
private String formMediaPath;
private String language;
private String emptyInstanceFilePath;
private String formType;
private Form(Form.Builder builder) {
displayName = builder.mDisplayName;
......@@ -51,6 +53,68 @@ public class Form {
jrCacheFilePath = builder.mJrCacheFilePath;
formMediaPath = builder.mFormMediaPath;
language = builder.mLanguage;
emptyInstanceFilePath = builder.mEmptyInstanceFilePath;
formType = builder.mFormType;
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
public String getJrFormId() {
return jrFormId;
}
public String getJrVersion() {
return jrVersion;
}
public String getFormFilePath() {
return formFilePath;
}
public String getSubmissionUri() {
return submissionUri;
}
public String getBASE64RSAPublicKey() {
return base64RSAPublicKey;
}
public String getDisplaySubtext() {
return displaySubtext;
}
public String getMD5Hash() {
return md5Hash;
}
public Long getDate() {
return date;
}
public String getJrCacheFilePath() {
return jrCacheFilePath;
}
public String getFormMediaPath() {
return formMediaPath;
}
public String getLanguage() {
return language;
}
public String getEmptyInstanceFilePath() {
return emptyInstanceFilePath;
}
public String getFormType() {
return formType;
}
public static class Builder {
......@@ -67,6 +131,8 @@ public class Form {
private String mJrCacheFilePath;
private String mFormMediaPath;
private String mLanguage;
private String mEmptyInstanceFilePath;
private String mFormType;
public Builder displayName(String displayName) {
mDisplayName = displayName;
......@@ -135,60 +201,18 @@ public class Form {
return this;
}
public Form build() {
return new Form(this);
public Builder emptyInstanceFilePath(String emptyInstanceFilePath) {
this.mEmptyInstanceFilePath = emptyInstanceFilePath;
return this;
}
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
public String getJrFormId() {
return jrFormId;
}
public String getJrVersion() {
return jrVersion;
}
public String getFormFilePath() {
return formFilePath;
}
public String getSubmissionUri() {
return submissionUri;
}
public String getBASE64RSAPublicKey() {
return base64RSAPublicKey;
}
public String getDisplaySubtext() {
return displaySubtext;
}
public String getMD5Hash() {
return md5Hash;
}
public Long getDate() {
return date;
}
public String getJrCacheFilePath() {
return jrCacheFilePath;
}
public String getFormMediaPath() {
return formMediaPath;
}
public Builder formType(String formType) {
this.mFormType = formType;
return this;
}
public String getLanguage() {
return language;
public Form build() {
return new Form(this);
}
}
}
......@@ -48,7 +48,7 @@ public class FormsProvider extends ContentProvider {
private static final String DATABASE_NAME = "forms.db";
private static final int DATABASE_VERSION = 4;
private static final int DATABASE_VERSION = 5;
private static final String FORMS_TABLE_NAME = "forms";
private static HashMap<String, String> sFormsProjectionMap;
......@@ -94,6 +94,7 @@ public class FormsProvider extends ContentProvider {
+ FormsColumns.LANGUAGE + " text, "
+ FormsColumns.SUBMISSION_URI + " text, "
+ FormsColumns.BASE64_RSA_PUBLIC_KEY + " text, "
+ FormsColumns.FORM_TYPE + " text, " // RAHADI
+ FormsColumns.EMPTY_INSTANCE_FILE_PATH + " text, " // RAHADI
+ FormsColumns.JRCACHE_FILE_PATH + " text not null );");
}
......@@ -142,6 +143,8 @@ public class FormsProvider extends ContentProvider {
: (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
+ FormsColumns.JRCACHE_FILE_PATH
+ ", "
+ FormsColumns.FORM_TYPE
+ ", "
+ FormsColumns.EMPTY_INSTANCE_FILE_PATH
+ ") SELECT "
+ FormsColumns._ID
......@@ -176,6 +179,8 @@ public class FormsProvider extends ContentProvider {
: (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
+ FormsColumns.JRCACHE_FILE_PATH
+ ", "
+ FormsProviderAPI.FORM_TYPE_DEFAULT
+ ", "
+ FormsColumns.EMPTY_INSTANCE_FILE_PATH
+ " FROM "
+ FORMS_TABLE_NAME);
......@@ -207,6 +212,7 @@ public class FormsProvider extends ContentProvider {
+ FormsColumns.JR_VERSION + ", "
+ FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
+ FormsColumns.JRCACHE_FILE_PATH + ", "
+ FormsColumns.FORM_TYPE + ", "
+ FormsColumns.EMPTY_INSTANCE_FILE_PATH + ") SELECT "
+ FormsColumns._ID + ", "
+ FormsColumns.DISPLAY_NAME
......@@ -228,6 +234,7 @@ public class FormsProvider extends ContentProvider {
+ FormsColumns.JR_VERSION + ", "
+ FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
+ FormsColumns.JRCACHE_FILE_PATH + ", "
+ FormsColumns.FORM_TYPE + ", "
+ FormsColumns.EMPTY_INSTANCE_FILE_PATH + " FROM "
+ TEMP_FORMS_TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + TEMP_FORMS_TABLE_NAME);
......
......@@ -25,6 +25,9 @@ import android.provider.BaseColumns;
public final class FormsProviderAPI {
public static final String AUTHORITY = "id.ac.stis.capi.odk.provider.odk.forms";
public static final String FORM_TYPE_DEFAULT = "default";
public static final String FORM_TYPE_UPDATING = "updating";
// This class cannot be instantiated
private FormsProviderAPI() {
}
......@@ -59,6 +62,7 @@ public final class FormsProviderAPI {
//RAHADI
public static final String EMPTY_INSTANCE_FILE_PATH = "emptyInstanceFilePath"; // can be null
public static final String FORM_TYPE = "formType";
// this is null on create, and can only be set on an update.
public static final String LANGUAGE = "language";
......
/*
* Copyright (C) 2009 University of Washington
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
......@@ -20,14 +20,6 @@ import android.database.SQLException;
import android.net.Uri;
import android.os.AsyncTask;
import id.ac.stis.capi.R;
import id.ac.stis.capi.odk.application.Collect;
import id.ac.stis.capi.odk.dao.FormsDao;
import id.ac.stis.capi.odk.listeners.DiskSyncListener;
import id.ac.stis.capi.odk.provider.FormsProviderAPI.FormsColumns;
import id.ac.stis.capi.odk.utilities.FileUtils;
import id.ac.stis.capi.odk.utilities.UrlUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
......@@ -35,6 +27,15 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import id.ac.stis.capi.R;
import id.ac.stis.capi.lessthink.utils.XmlUtils;
import id.ac.stis.capi.odk.application.Collect;
import id.ac.stis.capi.odk.dao.FormsDao;
import id.ac.stis.capi.odk.listeners.DiskSyncListener;
import id.ac.stis.capi.odk.logic.FormDetails;
import id.ac.stis.capi.odk.provider.FormsProviderAPI.FormsColumns;
import id.ac.stis.capi.odk.utilities.FileUtils;
import id.ac.stis.capi.odk.utilities.UrlUtils;
import timber.log.Timber;
/**
......@@ -302,6 +303,32 @@ public class DiskSyncTask extends AsyncTask<Void, String, String> {
// update will automatically update the .md5 and the cache path.
updateValues.put(FormsColumns.FORM_FILE_PATH, formDefFile.getAbsolutePath());
// RAHADI
File emptyFile = new File(Collect.EMPTY_INSTANCES_PATH + File.separator + title + ".xml");
if (emptyFile.exists()) {
int j = 2;
while (emptyFile.exists()) {
emptyFile = new File(Collect.EMPTY_INSTANCES_PATH + File.separator + title + "_" + j + ".xml");
j++;
}
emptyFile = new File(Collect.EMPTY_INSTANCES_PATH + File.separator + title + "_" + (j - 1) + ".xml");
} else {
emptyFile = XmlUtils.generateEmptyInstanceFile(formid, title);
}
if (emptyFile != null && emptyFile.exists()) {
updateValues.put(FormsColumns.EMPTY_INSTANCE_FILE_PATH, emptyFile.getAbsolutePath());
}
String formType = fields.get(FileUtils.FORM_TYPE);
if (formType != null) {
updateValues.put(FormsColumns.FORM_TYPE, formType);
} else {
throw new IllegalArgumentException(
Collect.getInstance().getString(R.string.xform_parse_error,
formDefFile.getName(), "type"));
}
return updateValues;
}
......
......@@ -21,17 +21,6 @@ import android.os.AsyncTask;
import org.javarosa.xform.parse.XFormParser;
import org.kxml2.kdom.Element;
import id.ac.stis.capi.R;
import id.ac.stis.capi.odk.application.Collect;
import id.ac.stis.capi.odk.dao.FormsDao;
import id.ac.stis.capi.odk.exception.TaskCancelledException;
import id.ac.stis.capi.odk.listeners.FormDownloaderListener;
import id.ac.stis.capi.odk.logic.FormDetails;
import id.ac.stis.capi.odk.provider.FormsProviderAPI.FormsColumns;
import id.ac.stis.capi.odk.utilities.DocumentFetchResult;
import id.ac.stis.capi.odk.utilities.FileUtils;
import id.ac.stis.capi.odk.utilities.UrlUtils;
import id.ac.stis.capi.odk.utilities.WebUtils;
import org.opendatakit.httpclientandroidlib.Header;
import org.opendatakit.httpclientandroidlib.HttpEntity;
import org.opendatakit.httpclientandroidlib.HttpResponse;
......@@ -54,6 +43,18 @@ import java.util.HashMap;
import java.util.List;
import java.util.zip.GZIPInputStream;
import id.ac.stis.capi.R;
import id.ac.stis.capi.lessthink.utils.XmlUtils;
import id.ac.stis.capi.odk.application.Collect;
import id.ac.stis.capi.odk.dao.FormsDao;
import id.ac.stis.capi.odk.exception.TaskCancelledException;
import id.ac.stis.capi.odk.listeners.FormDownloaderListener;
import id.ac.stis.capi.odk.logic.FormDetails;
import id.ac.stis.capi.odk.provider.FormsProviderAPI.FormsColumns;
import id.ac.stis.capi.odk.utilities.DocumentFetchResult;
import id.ac.stis.capi.odk.utilities.FileUtils;
import id.ac.stis.capi.odk.utilities.UrlUtils;
import id.ac.stis.capi.odk.utilities.WebUtils;
import timber.log.Timber;
/**
......@@ -70,13 +71,10 @@ public class DownloadFormsTask extends
private static final String MD5_COLON_PREFIX = "md5:";
private static final String TEMP_DOWNLOAD_EXTENSION = ".tempDownload";
private FormDownloaderListener stateListener;
private FormsDao formsDao;
private static final String NAMESPACE_OPENROSA_ORG_XFORMS_XFORMS_MANIFEST =
"http://openrosa.org/xforms/xformsManifest";
private FormDownloaderListener stateListener;
private FormsDao formsDao;
private boolean isXformsManifestNamespacedElement(Element e) {
return e.getNamespace().equalsIgnoreCase(NAMESPACE_OPENROSA_ORG_XFORMS_XFORMS_MANIFEST);
......@@ -289,15 +287,21 @@ public class DownloadFormsTask extends
v.put(FormsColumns.SUBMISSION_URI, formInfo.get(FileUtils.SUBMISSIONURI));
v.put(FormsColumns.BASE64_RSA_PUBLIC_KEY,
formInfo.get(FileUtils.BASE64_RSA_PUBLIC_KEY));
v.put(FormsColumns.FORM_TYPE, formInfo.get(FileUtils.FORM_TYPE));
File emptyFile = XmlUtils.generateEmptyInstanceFile(formInfo.get(FileUtils.FORMID),
formInfo.get(FileUtils.TITLE));
v.put(FormsColumns.EMPTY_INSTANCE_FILE_PATH, emptyFile.getAbsolutePath());
uri = formsDao.saveForm(v);
Collect.getInstance().getActivityLogger().logAction(this, "insert",
formFile.getAbsolutePath());
} else {
cursor.moveToFirst();
uri =
Uri.withAppendedPath(FormsColumns.CONTENT_URI,
cursor.getString(cursor.getColumnIndex(FormsColumns._ID)));
uri = Uri.withAppendedPath(FormsColumns.CONTENT_URI,
cursor.getString(cursor.getColumnIndex(FormsColumns._ID)));
mediaPath = cursor.getString(cursor.getColumnIndex(FormsColumns.FORM_MEDIA_PATH));
Collect.getInstance().getActivityLogger().logAction(this, "refresh",
formFile.getAbsolutePath());
......@@ -514,67 +518,9 @@ public class DownloadFormsTask extends
}
}
private static class UriResult {
private final Uri uri;
private final String mediaPath;
private final boolean isNew;
private UriResult(Uri uri, String mediaPath, boolean isNew) {
this.uri = uri;
this.mediaPath = mediaPath;
this.isNew = isNew;
}
private Uri getUri() {
return uri;
}
private String getMediaPath() {
return mediaPath;
}
private boolean isNew() {
return isNew;
}
}
private static class FileResult {
private final File file;
private final boolean isNew;
private FileResult(File file, boolean isNew) {
this.file = file;
this.isNew = isNew;
}
private File getFile() {
return file;
}
private boolean isNew() {
return isNew;
}
}
private static class MediaFile {
final String filename;
final String hash;
final String downloadUrl;
MediaFile(String filename, String hash, String downloadUrl) {
this.filename = filename;
this.hash = hash;
this.downloadUrl = downloadUrl;
}
}