add HttpUploadManager slot request
This commit is contained in:
parent
f9b3d42a8a
commit
f1fbf15fea
|
@ -8,6 +8,7 @@ import androidx.room.Query;
|
||||||
import androidx.room.Transaction;
|
import androidx.room.Transaction;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import im.conversations.android.database.entity.DiscoEntity;
|
import im.conversations.android.database.entity.DiscoEntity;
|
||||||
import im.conversations.android.database.entity.DiscoExtensionEntity;
|
import im.conversations.android.database.entity.DiscoExtensionEntity;
|
||||||
import im.conversations.android.database.entity.DiscoExtensionFieldEntity;
|
import im.conversations.android.database.entity.DiscoExtensionFieldEntity;
|
||||||
|
@ -16,6 +17,7 @@ import im.conversations.android.database.entity.DiscoFeatureEntity;
|
||||||
import im.conversations.android.database.entity.DiscoIdentityEntity;
|
import im.conversations.android.database.entity.DiscoIdentityEntity;
|
||||||
import im.conversations.android.database.entity.DiscoItemEntity;
|
import im.conversations.android.database.entity.DiscoItemEntity;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
|
import im.conversations.android.database.model.DiscoItemWithExtension;
|
||||||
import im.conversations.android.xmpp.Entity;
|
import im.conversations.android.xmpp.Entity;
|
||||||
import im.conversations.android.xmpp.EntityCapabilities;
|
import im.conversations.android.xmpp.EntityCapabilities;
|
||||||
import im.conversations.android.xmpp.EntityCapabilities2;
|
import im.conversations.android.xmpp.EntityCapabilities2;
|
||||||
|
@ -24,7 +26,9 @@ import im.conversations.android.xmpp.model.data.Value;
|
||||||
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
|
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
|
||||||
import im.conversations.android.xmpp.model.disco.items.Item;
|
import im.conversations.android.xmpp.model.disco.items.Item;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
|
||||||
|
@ -223,4 +227,11 @@ public abstract class DiscoDao {
|
||||||
"Discovering features for %s is not implemented",
|
"Discovering features for %s is not implemented",
|
||||||
entity.getClass().getName()));
|
entity.getClass().getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT disco_item.discoId,address FROM disco_item JOIN disco_feature ON"
|
||||||
|
+ " disco_item.discoId=disco_feature.discoId WHERE feature=:feature AND"
|
||||||
|
+ " (address=:address OR parentAddress=:address)")
|
||||||
|
public abstract ListenableFuture<List<DiscoItemWithExtension>> getItemByFeature(
|
||||||
|
DomainBareJid address, final String feature);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import androidx.room.Relation;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import im.conversations.android.database.entity.DiscoExtensionFieldEntity;
|
||||||
|
import im.conversations.android.database.entity.DiscoExtensionFieldValueEntity;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DiscoExtension {
|
||||||
|
|
||||||
|
public long id;
|
||||||
|
public String type;
|
||||||
|
|
||||||
|
@Relation(
|
||||||
|
entity = DiscoExtensionFieldEntity.class,
|
||||||
|
parentColumn = "id",
|
||||||
|
entityColumn = "extensionId")
|
||||||
|
public List<Field> fields;
|
||||||
|
|
||||||
|
public Field getField(final String name) {
|
||||||
|
return Iterables.find(fields, f -> name.equals(f.field), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Field {
|
||||||
|
|
||||||
|
public long id;
|
||||||
|
public String field;
|
||||||
|
|
||||||
|
@Relation(
|
||||||
|
entity = DiscoExtensionFieldValueEntity.class,
|
||||||
|
parentColumn = "id",
|
||||||
|
entityColumn = "fieldId",
|
||||||
|
projection = {"value"})
|
||||||
|
public List<String> values;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import androidx.room.Relation;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import im.conversations.android.database.entity.DiscoExtensionEntity;
|
||||||
|
import java.util.List;
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
|
|
||||||
|
public class DiscoItemWithExtension {
|
||||||
|
|
||||||
|
public Long discoId;
|
||||||
|
public Jid address;
|
||||||
|
|
||||||
|
@Relation(
|
||||||
|
entity = DiscoExtensionEntity.class,
|
||||||
|
parentColumn = "discoId",
|
||||||
|
entityColumn = "discoId")
|
||||||
|
public List<DiscoExtension> extensions;
|
||||||
|
|
||||||
|
public DiscoExtension getExtension(final String type) {
|
||||||
|
return Iterables.find(extensions, e -> type.equals(e.type), null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import im.conversations.android.xmpp.model.pubsub.event.Retract;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -86,7 +87,7 @@ public class BookmarkManager extends AbstractManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Void> publishBookmark(final Jid address, final boolean autoJoin) {
|
public ListenableFuture<Void> publishBookmark(final BareJid address, final boolean autoJoin) {
|
||||||
return publishBookmark(address, autoJoin, null);
|
return publishBookmark(address, autoJoin, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import im.conversations.android.BuildConfig;
|
import im.conversations.android.BuildConfig;
|
||||||
import im.conversations.android.R;
|
import im.conversations.android.R;
|
||||||
|
import im.conversations.android.database.model.DiscoItemWithExtension;
|
||||||
import im.conversations.android.xml.Namespace;
|
import im.conversations.android.xml.Namespace;
|
||||||
import im.conversations.android.xmpp.Entity;
|
import im.conversations.android.xmpp.Entity;
|
||||||
import im.conversations.android.xmpp.EntityCapabilities;
|
import im.conversations.android.xmpp.EntityCapabilities;
|
||||||
|
@ -240,6 +241,13 @@ public class DiscoManager extends AbstractManager {
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<List<DiscoItemWithExtension>> getServerItemByFeature(
|
||||||
|
final String feature) {
|
||||||
|
return getDatabase()
|
||||||
|
.discoDao()
|
||||||
|
.getItemByFeature(getAccount().address.asDomainBareJid(), feature);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasFeature(final Entity entity, final String feature) {
|
public boolean hasFeature(final Entity entity, final String feature) {
|
||||||
return getDatabase().discoDao().hasFeature(getAccount().id, entity, feature);
|
return getDatabase().discoDao().hasFeature(getAccount().id, entity, feature);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,166 @@
|
||||||
package im.conversations.android.xmpp.manager;
|
package im.conversations.android.xmpp.manager;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import im.conversations.android.database.model.DiscoItemWithExtension;
|
||||||
|
import im.conversations.android.xml.Namespace;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
import im.conversations.android.xmpp.model.upload.Get;
|
||||||
|
import im.conversations.android.xmpp.model.upload.Header;
|
||||||
|
import im.conversations.android.xmpp.model.upload.Put;
|
||||||
|
import im.conversations.android.xmpp.model.upload.Request;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class HttpUploadManager extends AbstractManager {
|
public class HttpUploadManager extends AbstractManager {
|
||||||
|
|
||||||
|
private static final List<String> ALLOWED_HEADERS =
|
||||||
|
Arrays.asList("Authorization", "Cookie", "Expires");
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(HttpUploadManager.class);
|
||||||
|
|
||||||
public HttpUploadManager(Context context, XmppConnection connection) {
|
public HttpUploadManager(Context context, XmppConnection connection) {
|
||||||
super(context, connection);
|
super(context, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Slot> request(
|
||||||
|
final String filename, final long size, final MediaType mediaType) {
|
||||||
|
return Futures.transformAsync(
|
||||||
|
getManager(DiscoManager.class).getServerItemByFeature(Namespace.HTTP_UPLOAD),
|
||||||
|
items -> {
|
||||||
|
final List<Service> services = Service.of(items);
|
||||||
|
final Service service =
|
||||||
|
Iterables.find(
|
||||||
|
services,
|
||||||
|
s -> s.maxFileSize == 0 || s.maxFileSize >= size,
|
||||||
|
null);
|
||||||
|
if (service == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"No upload service found that can handle files of size %d",
|
||||||
|
size));
|
||||||
|
}
|
||||||
|
LOGGER.info("Requesting slot from {}", service.address);
|
||||||
|
return request(service.address, filename, size, mediaType);
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<Slot> request(
|
||||||
|
final Jid address, final String filename, final long size, final MediaType mediaType) {
|
||||||
|
final var iq = new Iq(Iq.Type.GET);
|
||||||
|
iq.setTo(address);
|
||||||
|
final var request = iq.addExtension(new Request());
|
||||||
|
request.setFilename(filename);
|
||||||
|
request.setSize(size);
|
||||||
|
request.setContentType(mediaType.type());
|
||||||
|
final var iqFuture = connection.sendIqPacket(iq);
|
||||||
|
// catch and rethrow 'file-too-large'
|
||||||
|
return Futures.transform(
|
||||||
|
iqFuture,
|
||||||
|
result -> {
|
||||||
|
final var slot =
|
||||||
|
result.getExtension(
|
||||||
|
im.conversations.android.xmpp.model.upload.Slot.class);
|
||||||
|
if (slot == null) {
|
||||||
|
throw new IllegalStateException("No slot in response");
|
||||||
|
}
|
||||||
|
final var get = slot.getExtension(Get.class);
|
||||||
|
final var put = slot.getExtension(Put.class);
|
||||||
|
final var getUrl = get == null ? null : get.getUrl();
|
||||||
|
final var putUrl = put == null ? null : put.getUrl();
|
||||||
|
if (get == null || put == null) {
|
||||||
|
throw new IllegalStateException("Missing put or get URL in response");
|
||||||
|
}
|
||||||
|
final ImmutableMap.Builder<String, String> headers =
|
||||||
|
new ImmutableMap.Builder<>();
|
||||||
|
for (final Header header : put.getHeaders()) {
|
||||||
|
final String name = header.getHeaderName();
|
||||||
|
final String value = header.getContent();
|
||||||
|
if (value != null && ALLOWED_HEADERS.contains(name)) {
|
||||||
|
headers.put(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Slot(putUrl, headers.buildKeepingLast(), getUrl);
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Slot {
|
||||||
|
public final HttpUrl put;
|
||||||
|
public final Map<String, String> headers;
|
||||||
|
public final HttpUrl get;
|
||||||
|
|
||||||
|
public Slot(HttpUrl put, final Map<String, String> headers, final HttpUrl get) {
|
||||||
|
this.put = put;
|
||||||
|
this.headers = headers;
|
||||||
|
this.get = get;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("put", put)
|
||||||
|
.add("headers", headers)
|
||||||
|
.add("get", get)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Service {
|
||||||
|
public final Jid address;
|
||||||
|
public final long maxFileSize;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("address", address)
|
||||||
|
.add("maxFileSize", maxFileSize)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Service(Jid address, long maxFileSize) {
|
||||||
|
this.address = address;
|
||||||
|
this.maxFileSize = maxFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Service> of(List<DiscoItemWithExtension> items) {
|
||||||
|
return Lists.transform(items, Service::of);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Service of(final DiscoItemWithExtension item) {
|
||||||
|
final var discoExtension = item.getExtension(Namespace.HTTP_UPLOAD);
|
||||||
|
final long maxFileSize;
|
||||||
|
if (discoExtension == null) {
|
||||||
|
maxFileSize = 0;
|
||||||
|
} else {
|
||||||
|
final var field = discoExtension.getField("max-file-size");
|
||||||
|
final var value = field == null ? null : Iterables.getFirst(field.values, null);
|
||||||
|
if (Strings.isNullOrEmpty(value)) {
|
||||||
|
maxFileSize = 0;
|
||||||
|
} else {
|
||||||
|
maxFileSize = Longs.tryParse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Service(item.address, maxFileSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,8 @@ public class MultiUserChatManager extends AbstractManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enterExisting(final MucWithNick mucWithNick, final InfoQuery infoQuery) {
|
private void enterExisting(final MucWithNick mucWithNick, final InfoQuery infoQuery) {
|
||||||
if (infoQuery.hasFeature(Namespace.MUC)) {
|
if (infoQuery.hasFeature(Namespace.MUC)
|
||||||
|
&& infoQuery.hasIdentityWithCategory("conference")) {
|
||||||
sendJoinPresence(mucWithNick);
|
sendJoinPresence(mucWithNick);
|
||||||
} else {
|
} else {
|
||||||
getDatabase().chatDao().setMucState(mucWithNick.chatId, MucState.NOT_A_MUC);
|
getDatabase().chatDao().setMucState(mucWithNick.chatId, MucState.NOT_A_MUC);
|
||||||
|
@ -91,8 +92,6 @@ public class MultiUserChatManager extends AbstractManager {
|
||||||
final MucUser mucUser = presencePacket.getExtension(MucUser.class);
|
final MucUser mucUser = presencePacket.getExtension(MucUser.class);
|
||||||
Preconditions.checkArgument(
|
Preconditions.checkArgument(
|
||||||
mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE));
|
mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE));
|
||||||
// TODO flag chat as joined
|
|
||||||
LOGGER.info("Received self presence for {}", presencePacket.getFrom());
|
|
||||||
final var database = getDatabase();
|
final var database = getDatabase();
|
||||||
database.runInTransaction(
|
database.runInTransaction(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -107,7 +106,6 @@ public class MultiUserChatManager extends AbstractManager {
|
||||||
"Available presence received for archived or non existent chat");
|
"Available presence received for archived or non existent chat");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO set status codes
|
|
||||||
database.chatDao()
|
database.chatDao()
|
||||||
.setMucState(
|
.setMucState(
|
||||||
chatIdentifier.id, MucState.AVAILABLE, mucUser.getStatus());
|
chatIdentifier.id, MucState.AVAILABLE, mucUser.getStatus());
|
||||||
|
@ -132,8 +130,11 @@ public class MultiUserChatManager extends AbstractManager {
|
||||||
} else if (chatIdentifier.archived) {
|
} else if (chatIdentifier.archived) {
|
||||||
database.chatDao().setMucState(chatIdentifier.id, null);
|
database.chatDao().setMucState(chatIdentifier.id, null);
|
||||||
} else {
|
} else {
|
||||||
// TODO set status codes
|
database.chatDao()
|
||||||
database.chatDao().setMucState(chatIdentifier.id, MucState.UNAVAILABLE);
|
.setMucState(
|
||||||
|
chatIdentifier.id,
|
||||||
|
MucState.UNAVAILABLE,
|
||||||
|
mucUser.getStatus());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,8 @@ public class InfoQuery extends Extension {
|
||||||
public Collection<Identity> getIdentities() {
|
public Collection<Identity> getIdentities() {
|
||||||
return this.getExtensions(Identity.class);
|
return this.getExtensions(Identity.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasIdentityWithCategory(final String category) {
|
||||||
|
return Iterables.any(getIdentities(), i -> category.equals(i.getCategory()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package im.conversations.android.xmpp.model.upload;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Get extends Extension {
|
||||||
|
|
||||||
|
public Get() {
|
||||||
|
super(Get.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpUrl getUrl() {
|
||||||
|
final var url = this.getAttribute("url");
|
||||||
|
if (Strings.isNullOrEmpty(url)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return HttpUrl.parse(url);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package im.conversations.android.xmpp.model.upload;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Header extends Extension {
|
||||||
|
|
||||||
|
public Header() {
|
||||||
|
super(Header.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeaderName() {
|
||||||
|
return this.getAttribute("name");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package im.conversations.android.xmpp.model.upload;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.util.Collection;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Put extends Extension {
|
||||||
|
|
||||||
|
public Put() {
|
||||||
|
super(Put.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpUrl getUrl() {
|
||||||
|
final var url = this.getAttribute("url");
|
||||||
|
if (Strings.isNullOrEmpty(url)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return HttpUrl.parse(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Header> getHeaders() {
|
||||||
|
return this.getExtensions(Header.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package im.conversations.android.xmpp.model.upload;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Request extends Extension {
|
||||||
|
|
||||||
|
public Request() {
|
||||||
|
super(Request.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilename(String filename) {
|
||||||
|
this.setAttribute("filename", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(long size) {
|
||||||
|
this.setAttribute("size", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentType(String type) {
|
||||||
|
this.setAttribute("content-ype", type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.upload;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Slot extends Extension {
|
||||||
|
|
||||||
|
public Slot() {
|
||||||
|
super(Slot.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@XmlPackage(namespace = Namespace.HTTP_UPLOAD)
|
||||||
|
package im.conversations.android.xmpp.model.upload;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
||||||
|
import im.conversations.android.xml.Namespace;
|
Loading…
Reference in a new issue