mirror of
https://github.com/etesync/android
synced 2025-02-25 13:52:30 +00:00
Bug fixes, refactoring, version bump
* don't manually decode URLs for comparing (because we now always use encoded URLs) * .ics: VERSION before PRODID * refactoring: move WebDavResource.httpClient to new singleton DavHttpClient * close input streams in propfind/multi-get * version bumpt to 0.4.1
This commit is contained in:
parent
66d6ec34d1
commit
1fc2679bd4
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="at.bitfire.davdroid"
|
package="at.bitfire.davdroid"
|
||||||
android:versionCode="13"
|
android:versionCode="14"
|
||||||
android:versionName="0.4-alpha" >
|
android:versionName="0.4.1-alpha" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="14"
|
android:minSdkVersion="14"
|
||||||
|
@ -9,7 +9,7 @@ package at.bitfire.davdroid;
|
|||||||
|
|
||||||
public class Constants {
|
public class Constants {
|
||||||
public static final String
|
public static final String
|
||||||
APP_VERSION = "0.4-alpha",
|
APP_VERSION = "0.4.1-alpha",
|
||||||
|
|
||||||
ACCOUNT_TYPE = "bitfire.at.davdroid",
|
ACCOUNT_TYPE = "bitfire.at.davdroid",
|
||||||
|
|
||||||
|
@ -10,15 +10,6 @@ import android.util.Log;
|
|||||||
public class URIUtils {
|
public class URIUtils {
|
||||||
private static final String TAG = "davdroid.URIUtils";
|
private static final String TAG = "davdroid.URIUtils";
|
||||||
|
|
||||||
public static boolean isSame(URI a, URI b) {
|
|
||||||
try {
|
|
||||||
a = new URI(a.getScheme(), null, a.getHost(), a.getPort(), sanitize(a.getPath()), sanitize(a.getQuery()), null);
|
|
||||||
b = new URI(b.getScheme(), null, b.getHost(), b.getPort(), sanitize(b.getPath()), sanitize(b.getQuery()), null);
|
|
||||||
return a.equals(b);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handles invalid URLs/paths as good as possible
|
// handles invalid URLs/paths as good as possible
|
||||||
public static String sanitize(String original) {
|
public static String sanitize(String original) {
|
||||||
|
@ -148,8 +148,8 @@ public class Event extends Resource {
|
|||||||
@Override
|
@Override
|
||||||
public String toEntity() {
|
public String toEntity() {
|
||||||
net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();
|
net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();
|
||||||
ical.getProperties().add(new ProdId("-//bitfire web engineering//DAVdroid " + Constants.APP_VERSION + "//EN"));
|
|
||||||
ical.getProperties().add(Version.VERSION_2_0);
|
ical.getProperties().add(Version.VERSION_2_0);
|
||||||
|
ical.getProperties().add(new ProdId("-//bitfire web engineering//DAVdroid " + Constants.APP_VERSION + "//EN"));
|
||||||
|
|
||||||
VEvent event = new VEvent();
|
VEvent event = new VEvent();
|
||||||
PropertyList props = event.getProperties();
|
PropertyList props = event.getProperties();
|
||||||
|
58
src/at/bitfire/davdroid/webdav/DavHttpClient.java
Normal file
58
src/at/bitfire/davdroid/webdav/DavHttpClient.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package at.bitfire.davdroid.webdav;
|
||||||
|
|
||||||
|
import org.apache.http.client.params.HttpClientParams;
|
||||||
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
|
import org.apache.http.conn.scheme.Scheme;
|
||||||
|
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
|
import org.apache.http.params.BasicHttpParams;
|
||||||
|
import org.apache.http.params.CoreProtocolPNames;
|
||||||
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
|
import org.apache.http.params.HttpParams;
|
||||||
|
|
||||||
|
import at.bitfire.davdroid.Constants;
|
||||||
|
import at.bitfire.davdroid.webdav.GzipDecompressingEntity;
|
||||||
|
import at.bitfire.davdroid.webdav.TlsSniSocketFactory;
|
||||||
|
|
||||||
|
// see AndroidHttpClient
|
||||||
|
|
||||||
|
|
||||||
|
public class DavHttpClient extends DefaultHttpClient {
|
||||||
|
private static DavHttpClient httpClient = null;
|
||||||
|
|
||||||
|
private DavHttpClient(ClientConnectionManager connectionManager, HttpParams params) {
|
||||||
|
super(connectionManager, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static synchronized DefaultHttpClient getInstance() {
|
||||||
|
if (httpClient == null) {
|
||||||
|
HttpParams params = new BasicHttpParams();
|
||||||
|
params.setParameter(CoreProtocolPNames.USER_AGENT, "DAVdroid/" + Constants.APP_VERSION);
|
||||||
|
|
||||||
|
// use defaults of AndroidHttpClient
|
||||||
|
HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
|
||||||
|
HttpConnectionParams.setSoTimeout(params, 20 * 1000);
|
||||||
|
HttpConnectionParams.setSocketBufferSize(params, 8192);
|
||||||
|
HttpConnectionParams.setStaleCheckingEnabled(params, false);
|
||||||
|
|
||||||
|
// don't allow redirections
|
||||||
|
HttpClientParams.setRedirecting(params, false);
|
||||||
|
|
||||||
|
// use our own, SNI-capable LayeredSocketFactory for https://
|
||||||
|
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||||
|
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
|
||||||
|
schemeRegistry.register(new Scheme("https", new TlsSniSocketFactory(), 443));
|
||||||
|
|
||||||
|
httpClient = new DavHttpClient(new ThreadSafeClientConnManager(params, schemeRegistry), params);
|
||||||
|
|
||||||
|
// allow gzip compression
|
||||||
|
GzipDecompressingEntity.enable(httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,7 @@ import org.apache.http.HttpException;
|
|||||||
public class InvalidDavResponseException extends HttpException {
|
public class InvalidDavResponseException extends HttpException {
|
||||||
private static final long serialVersionUID = -2118919144443165706L;
|
private static final long serialVersionUID = -2118919144443165706L;
|
||||||
|
|
||||||
public InvalidDavResponseException() {
|
public InvalidDavResponseException(String message) {
|
||||||
super("Invalid DAV response");
|
super("Invalid DAV response: " + message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,18 +39,13 @@ import org.apache.http.client.methods.HttpDelete;
|
|||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpOptions;
|
import org.apache.http.client.methods.HttpOptions;
|
||||||
import org.apache.http.client.methods.HttpPut;
|
import org.apache.http.client.methods.HttpPut;
|
||||||
import org.apache.http.client.params.ClientPNames;
|
|
||||||
import org.apache.http.conn.scheme.Scheme;
|
|
||||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
|
||||||
import org.apache.http.entity.ByteArrayEntity;
|
import org.apache.http.entity.ByteArrayEntity;
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
import org.apache.http.message.BasicLineParser;
|
import org.apache.http.message.BasicLineParser;
|
||||||
import org.apache.http.params.CoreProtocolPNames;
|
|
||||||
import org.simpleframework.xml.Serializer;
|
import org.simpleframework.xml.Serializer;
|
||||||
import org.simpleframework.xml.core.Persister;
|
import org.simpleframework.xml.core.Persister;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import at.bitfire.davdroid.Constants;
|
|
||||||
import at.bitfire.davdroid.URIUtils;
|
import at.bitfire.davdroid.URIUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -91,7 +86,7 @@ public class WebDavResource {
|
|||||||
// content (available after GET)
|
// content (available after GET)
|
||||||
@Getter protected InputStream content;
|
@Getter protected InputStream content;
|
||||||
|
|
||||||
protected DefaultHttpClient client;
|
protected DefaultHttpClient client = DavHttpClient.getInstance();
|
||||||
|
|
||||||
|
|
||||||
public WebDavResource(URI baseURL, boolean trailingSlash) throws URISyntaxException {
|
public WebDavResource(URI baseURL, boolean trailingSlash) throws URISyntaxException {
|
||||||
@ -99,20 +94,6 @@ public class WebDavResource {
|
|||||||
|
|
||||||
if (trailingSlash && !location.getRawPath().endsWith("/"))
|
if (trailingSlash && !location.getRawPath().endsWith("/"))
|
||||||
location = new URI(location.getScheme(), location.getSchemeSpecificPart() + "/", null);
|
location = new URI(location.getScheme(), location.getSchemeSpecificPart() + "/", null);
|
||||||
|
|
||||||
// create new HTTP client
|
|
||||||
client = new DefaultHttpClient();
|
|
||||||
client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "DAVdroid/" + Constants.APP_VERSION);
|
|
||||||
|
|
||||||
// use our own, SNI-capable LayeredSocketFactory for https://
|
|
||||||
SchemeRegistry schemeRegistry = client.getConnectionManager().getSchemeRegistry();
|
|
||||||
schemeRegistry.register(new Scheme("https", new TlsSniSocketFactory(), 443));
|
|
||||||
|
|
||||||
// allow gzip compression
|
|
||||||
GzipDecompressingEntity.enable(client);
|
|
||||||
|
|
||||||
// redirections
|
|
||||||
client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebDavResource(URI baseURL, String username, String password, boolean preemptive, boolean trailingSlash) throws URISyntaxException {
|
public WebDavResource(URI baseURL, String username, String password, boolean preemptive, boolean trailingSlash) throws URISyntaxException {
|
||||||
@ -130,12 +111,10 @@ public class WebDavResource {
|
|||||||
|
|
||||||
protected WebDavResource(WebDavResource parent, URI uri) {
|
protected WebDavResource(WebDavResource parent, URI uri) {
|
||||||
location = uri;
|
location = uri;
|
||||||
client = parent.client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebDavResource(WebDavResource parent, String member) {
|
public WebDavResource(WebDavResource parent, String member) {
|
||||||
location = parent.location.resolve(URIUtils.sanitize(member));
|
location = parent.location.resolve(URIUtils.sanitize(member));
|
||||||
client = parent.client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebDavResource(WebDavResource parent, String member, boolean trailingSlash) {
|
public WebDavResource(WebDavResource parent, String member, boolean trailingSlash) {
|
||||||
@ -261,29 +240,36 @@ public class WebDavResource {
|
|||||||
|
|
||||||
/* collection operations */
|
/* collection operations */
|
||||||
|
|
||||||
public boolean propfind(HttpPropfind.Mode mode) throws IOException, InvalidDavResponseException, HttpException {
|
public void propfind(HttpPropfind.Mode mode) throws IOException, InvalidDavResponseException, HttpException {
|
||||||
HttpPropfind propfind = new HttpPropfind(location, mode);
|
HttpPropfind propfind = new HttpPropfind(location, mode);
|
||||||
HttpResponse response = client.execute(propfind);
|
HttpResponse response = client.execute(propfind);
|
||||||
checkResponse(response);
|
checkResponse(response);
|
||||||
|
|
||||||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) {
|
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) {
|
||||||
|
InputStream content = response.getEntity().getContent();
|
||||||
|
if (content == null)
|
||||||
|
throw new InvalidDavResponseException("Multistatus response without content");
|
||||||
|
|
||||||
|
// duplicate content for logging
|
||||||
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
|
InputStream is = new TeeInputStream(content, logStream);
|
||||||
|
|
||||||
DavMultistatus multistatus;
|
DavMultistatus multistatus;
|
||||||
try {
|
try {
|
||||||
Serializer serializer = new Persister();
|
Serializer serializer = new Persister();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
InputStream is = new TeeInputStream(response.getEntity().getContent(), baos);
|
|
||||||
multistatus = serializer.read(DavMultistatus.class, is, false);
|
multistatus = serializer.read(DavMultistatus.class, is, false);
|
||||||
|
|
||||||
Log.d(TAG, "Received multistatus response: " + baos.toString("UTF-8"));
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Log.w(TAG, "Invalid PROPFIND XML response", ex);
|
Log.w(TAG, "Invalid PROPFIND XML response", ex);
|
||||||
throw new InvalidDavResponseException();
|
throw new InvalidDavResponseException("Invalid PROPFIND response");
|
||||||
|
} finally {
|
||||||
|
Log.d(TAG, "Received multistatus response:\n" + logStream.toString("UTF-8"));
|
||||||
|
is.close();
|
||||||
|
content.close();
|
||||||
}
|
}
|
||||||
processMultiStatus(multistatus);
|
processMultiStatus(multistatus);
|
||||||
return true;
|
|
||||||
|
|
||||||
} else
|
} else
|
||||||
return false;
|
throw new InvalidDavResponseException("Multistatus response expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void multiGet(String[] names, MultigetType type) throws IOException, InvalidDavResponseException, HttpException {
|
public void multiGet(String[] names, MultigetType type) throws IOException, InvalidDavResponseException, HttpException {
|
||||||
@ -307,7 +293,7 @@ public class WebDavResource {
|
|||||||
serializer.write(multiget, writer);
|
serializer.write(multiget, writer);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Log.e(TAG, "Couldn't create XML multi-get request", ex);
|
Log.e(TAG, "Couldn't create XML multi-get request", ex);
|
||||||
throw new InvalidDavResponseException();
|
throw new InvalidDavResponseException("Couldn't create multi-get request");
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpReport report = new HttpReport(location, writer.toString());
|
HttpReport report = new HttpReport(location, writer.toString());
|
||||||
@ -315,21 +301,30 @@ public class WebDavResource {
|
|||||||
checkResponse(response);
|
checkResponse(response);
|
||||||
|
|
||||||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) {
|
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) {
|
||||||
DavMultistatus multistatus;
|
InputStream content = response.getEntity().getContent();
|
||||||
try {
|
if (content == null)
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
throw new InvalidDavResponseException("Multistatus response without content");
|
||||||
InputStream is = new TeeInputStream(response.getEntity().getContent(), baos);
|
|
||||||
multistatus = serializer.read(DavMultistatus.class, is, false);
|
|
||||||
|
|
||||||
Log.d(TAG, "Received multistatus response: " + baos.toString("UTF-8"));
|
DavMultistatus multistatus;
|
||||||
|
|
||||||
|
// duplicate content for logging
|
||||||
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
|
InputStream is = new TeeInputStream(content, logStream, true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
multistatus = serializer.read(DavMultistatus.class, is, false);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Log.e(TAG, "Couldn't parse multi-get response", ex);
|
Log.e(TAG, "Couldn't parse multi-get response", ex);
|
||||||
throw new InvalidDavResponseException();
|
throw new InvalidDavResponseException("Invalid multi-get response");
|
||||||
|
} finally {
|
||||||
|
Log.d(TAG, "Received multistatus response:\n" + logStream.toString("UTF-8"));
|
||||||
|
is.close();
|
||||||
|
content.close();
|
||||||
}
|
}
|
||||||
processMultiStatus(multistatus);
|
processMultiStatus(multistatus);
|
||||||
|
|
||||||
} else
|
} else
|
||||||
throw new InvalidDavResponseException();
|
throw new InvalidDavResponseException("Multistatus response expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -393,7 +388,7 @@ public class WebDavResource {
|
|||||||
|
|
||||||
// about which resource is this response?
|
// about which resource is this response?
|
||||||
WebDavResource referenced = null;
|
WebDavResource referenced = null;
|
||||||
if (URIUtils.isSame(location, href)) { // -> ourselves
|
if (location.equals(href)) { // -> ourselves
|
||||||
referenced = this;
|
referenced = this;
|
||||||
|
|
||||||
} else { // -> about a member
|
} else { // -> about a member
|
||||||
|
@ -17,11 +17,6 @@ public class URIUtilsTest extends InstrumentationTestCase {
|
|||||||
baseURI = new URI(BASE_URI);
|
baseURI = new URI(BASE_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void testIsSame() throws URISyntaxException {
|
|
||||||
assertTrue(URIUtils.isSame(new URI(ROOT_URI + "my@email/"), new URI(ROOT_URI + "my%40email/")));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSanitize() {
|
public void testSanitize() {
|
||||||
assertEquals("/my%40email.com/dir", URIUtils.sanitize("/my@email.com/dir"));
|
assertEquals("/my%40email.com/dir", URIUtils.sanitize("/my@email.com/dir"));
|
||||||
assertEquals("my%3Afile.vcf", URIUtils.sanitize("my:file.vcf"));
|
assertEquals("my%3Afile.vcf", URIUtils.sanitize("my:file.vcf"));
|
||||||
|
@ -82,10 +82,14 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testPropfindCurrentUserPrincipal() throws IOException, HttpException {
|
public void testPropfindCurrentUserPrincipal() throws IOException, HttpException {
|
||||||
assertTrue(davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL));
|
davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
|
||||||
assertEquals("/dav/principals/users/test", davCollection.getCurrentUserPrincipal());
|
assertEquals("/dav/principals/users/test", davCollection.getCurrentUserPrincipal());
|
||||||
|
|
||||||
assertFalse(simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL));
|
try {
|
||||||
|
simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
|
||||||
|
fail();
|
||||||
|
} catch(InvalidDavResponseException ex) {
|
||||||
|
}
|
||||||
assertNull(simpleFile.getCurrentUserPrincipal());
|
assertNull(simpleFile.getCurrentUserPrincipal());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +134,9 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
|||||||
WebDavResource redirection = new WebDavResource(new URI(ROBOHYDRA_BASE + "redirect"), false);
|
WebDavResource redirection = new WebDavResource(new URI(ROBOHYDRA_BASE + "redirect"), false);
|
||||||
try {
|
try {
|
||||||
redirection.get();
|
redirection.get();
|
||||||
} catch (HttpException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fail();
|
fail();
|
||||||
|
} catch (HttpException e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGet() throws URISyntaxException, IOException, HttpException {
|
public void testGet() throws URISyntaxException, IOException, HttpException {
|
||||||
@ -160,10 +163,9 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
|||||||
// should fail on an existing file
|
// should fail on an existing file
|
||||||
try {
|
try {
|
||||||
davExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE);
|
davExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE);
|
||||||
} catch(PreconditionFailedException ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fail();
|
fail();
|
||||||
|
} catch(PreconditionFailedException ex) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPutUpdateDontOverwrite() throws IOException, HttpException {
|
public void testPutUpdateDontOverwrite() throws IOException, HttpException {
|
||||||
@ -173,10 +175,9 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
|||||||
// should fail on a non-existing file
|
// should fail on a non-existing file
|
||||||
try {
|
try {
|
||||||
davNonExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE);
|
davNonExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE);
|
||||||
} catch(PreconditionFailedException ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fail();
|
fail();
|
||||||
|
} catch(PreconditionFailedException ex) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDelete() throws IOException, HttpException {
|
public void testDelete() throws IOException, HttpException {
|
||||||
@ -186,10 +187,9 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
|||||||
// should fail on a non-existing file
|
// should fail on a non-existing file
|
||||||
try {
|
try {
|
||||||
davNonExistingFile.delete();
|
davNonExistingFile.delete();
|
||||||
} catch (NotFoundException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fail();
|
fail();
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user