package org.whispersystems.textsecuregcm.storage;
import com.google.protobuf.ByteString;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.Binder;
import org.skife.jdbi.v2.sqlobject.BinderFactory;
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
import org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
import org.whispersystems.textsecuregcm.util.Pair;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public abstract class Messages {
private static final String ID = "id";
private static final String TYPE = "type";
private static final String RELAY = "relay";
private static final String TIMESTAMP = "timestamp";
private static final String SOURCE = "source";
private static final String SOURCE_DEVICE = "source_device";
private static final String DESTINATION = "destination";
private static final String DESTINATION_DEVICE = "destination_device";
private static final String MESSAGE = "message";
@SqlQuery("INSERT INTO messages (" + TYPE + ", " + RELAY + ", " + TIMESTAMP + ", " + SOURCE + ", " + SOURCE_DEVICE + ", " + DESTINATION + ", " + DESTINATION_DEVICE + ", " + MESSAGE + ") " +
"VALUES (:type, :relay, :timestamp, :source, :source_device, :destination, :destination_device, :message) " +
"RETURNING (SELECT COUNT(id) FROM messages WHERE " + DESTINATION + " = :destination AND " + DESTINATION_DEVICE + " = :destination_device AND " + TYPE + " != " + OutgoingMessageSignal.Type.RECEIPT_VALUE + ")")
abstract int store(@MessageBinder OutgoingMessageSignal message,
@Bind("destination") String destination,
@Bind("destination_device") long destinationDevice);
@Mapper(MessageMapper.class)
@SqlQuery("SELECT * FROM messages WHERE " + DESTINATION + " = :destination AND " + DESTINATION_DEVICE + " = :destination_device")
abstract List<Pair<Long, OutgoingMessageSignal>> load(@Bind("destination") String destination,
@Bind("destination_device") long destinationDevice);
@SqlUpdate("DELETE FROM messages WHERE " + ID + " = :id")
abstract void remove(@Bind("id") long id);
@SqlUpdate("DELETE FROM messages WHERE " + DESTINATION + " = :destination")
abstract void clear(@Bind("destination") String destination);
@SqlUpdate("VACUUM messages")
public abstract void vacuum();
public static class MessageMapper implements ResultSetMapper<Pair<Long, OutgoingMessageSignal>> {
@Override
public Pair<Long, OutgoingMessageSignal> map(int i, ResultSet resultSet, StatementContext statementContext)
throws SQLException
{
return new Pair<>(resultSet.getLong(ID),
OutgoingMessageSignal.newBuilder()
.setType(resultSet.getInt(TYPE))
.setRelay(resultSet.getString(RELAY))
.setTimestamp(resultSet.getLong(TIMESTAMP))
.setSource(resultSet.getString(SOURCE))
.setSourceDevice(resultSet.getInt(SOURCE_DEVICE))
.setMessage(ByteString.copyFrom(resultSet.getBytes(MESSAGE)))
.build());
}
}
@BindingAnnotation(MessageBinder.AccountBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface MessageBinder {
public static class AccountBinderFactory implements BinderFactory {
@Override
public Binder build(Annotation annotation) {
return new Binder<MessageBinder, OutgoingMessageSignal>() {
@Override
public void bind(SQLStatement<?> sql,
MessageBinder accountBinder,
OutgoingMessageSignal message)
{
sql.bind(TYPE, message.getType());
sql.bind(RELAY, message.getRelay());
sql.bind(TIMESTAMP, message.getTimestamp());
sql.bind(SOURCE, message.getSource());
sql.bind(SOURCE_DEVICE, message.getSourceDevice());
sql.bind(MESSAGE, message.getMessage().toByteArray());
}
};
}
}
}
}