anotherim-desktop/libdino/src/service/database.vala

465 lines
20 KiB
Vala
Raw Normal View History

2017-03-02 14:37:32 +00:00
using Gee;
using Qlite;
using Dino.Entities;
namespace Dino {
public class Database : Qlite.Database {
private const int VERSION = 8;
2017-03-02 14:37:32 +00:00
public class AccountTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<string> bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true };
public Column<string> resourcepart = new Column.Text("resourcepart");
public Column<string> password = new Column.Text("password");
public Column<string> alias = new Column.Text("alias");
public Column<bool> enabled = new Column.BoolInt("enabled");
2017-05-24 15:28:39 +00:00
public Column<string> roster_version = new Column.Text("roster_version") { min_version=2 };
2017-08-16 09:44:42 +00:00
public Column<long> mam_earliest_synced = new Column.Long("mam_earliest_synced") { min_version=4 };
2017-03-02 14:37:32 +00:00
internal AccountTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "account");
2017-08-16 09:44:42 +00:00
init({id, bare_jid, resourcepart, password, alias, enabled, roster_version, mam_earliest_synced});
2017-03-02 14:37:32 +00:00
}
}
public class JidTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<string> bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true };
internal JidTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "jid");
init({id, bare_jid});
}
}
public class ContentTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<int> conversation_id = new Column.Integer("conversation_id") { not_null = true };
public Column<long> time = new Column.Long("time") { not_null = true };
public Column<long> local_time = new Column.Long("local_time") { not_null = true };
public Column<int> content_type = new Column.Integer("content_type") { not_null = true };
public Column<int> foreign_id = new Column.Integer("foreign_id") { not_null = true };
internal ContentTable(Database db) {
base(db, "contentx");
init({id, conversation_id, time, local_time, content_type, foreign_id});
unique({content_type, foreign_id}, "IGNORE");
}
}
2017-03-02 14:37:32 +00:00
public class MessageTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<string> stanza_id = new Column.Text("stanza_id");
public Column<int> account_id = new Column.Integer("account_id") { not_null = true };
public Column<int> counterpart_id = new Column.Integer("counterpart_id") { not_null = true };
2017-03-02 14:37:32 +00:00
public Column<string> counterpart_resource = new Column.Text("counterpart_resource");
public Column<string> our_resource = new Column.Text("our_resource");
public Column<bool> direction = new Column.BoolInt("direction") { not_null = true };
2017-03-02 14:37:32 +00:00
public Column<int> type_ = new Column.Integer("type");
public Column<long> time = new Column.Long("time");
public Column<long> local_time = new Column.Long("local_time");
public Column<string> body = new Column.Text("body");
public Column<int> encryption = new Column.Integer("encryption");
public Column<int> marked = new Column.Integer("marked");
internal MessageTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "message");
init({id, stanza_id, account_id, counterpart_id, our_resource, counterpart_resource, direction,
type_, time, local_time, body, encryption, marked});
2017-08-29 22:03:37 +00:00
index("message_localtime_counterpart_idx", {local_time, counterpart_id});
2018-06-20 14:22:03 +00:00
fts({body});
2017-03-02 14:37:32 +00:00
}
}
public class RealJidTable : Table {
public Column<int> message_id = new Column.Integer("message_id") { primary_key = true };
public Column<string> real_jid = new Column.Text("real_jid");
internal RealJidTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "real_jid");
init({message_id, real_jid});
}
}
public class UndecryptedTable : Table {
public Column<int> message_id = new Column.Integer("message_id");
public Column<int> type_ = new Column.Integer("type");
public Column<string> data = new Column.Text("data");
internal UndecryptedTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "undecrypted");
init({message_id, type_, data});
}
}
2017-08-29 22:03:37 +00:00
public class FileTransferTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<int> account_id = new Column.Integer("account_id") { not_null = true };
public Column<int> counterpart_id = new Column.Integer("counterpart_id") { not_null = true };
public Column<string> counterpart_resource = new Column.Text("counterpart_resource");
public Column<string> our_resource = new Column.Text("our_resource");
public Column<bool> direction = new Column.BoolInt("direction") { not_null = true };
public Column<long> time = new Column.Long("time");
public Column<long> local_time = new Column.Long("local_time");
public Column<int> encryption = new Column.Integer("encryption");
public Column<string> file_name = new Column.Text("file_name");
public Column<string> path = new Column.Text("path");
public Column<string> mime_type = new Column.Text("mime_type");
public Column<int> size = new Column.Integer("size");
public Column<int> state = new Column.Integer("state");
public Column<int> provider = new Column.Integer("provider");
public Column<string> info = new Column.Text("info");
internal FileTransferTable(Database db) {
base(db, "file_transfer");
init({id, account_id, counterpart_id, counterpart_resource, our_resource, direction, time, local_time,
encryption, file_name, path, mime_type, size, state, provider, info});
index("filetransfer_localtime_counterpart_idx", {local_time, counterpart_id});
}
}
2017-03-02 14:37:32 +00:00
public class ConversationTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<int> account_id = new Column.Integer("account_id") { not_null = true };
public Column<int> jid_id = new Column.Integer("jid_id") { not_null = true };
public Column<string> resource = new Column.Text("resource") { min_version=1 };
2017-03-02 14:37:32 +00:00
public Column<bool> active = new Column.BoolInt("active");
public Column<long> last_active = new Column.Long("last_active");
public Column<int> type_ = new Column.Integer("type");
public Column<int> encryption = new Column.Integer("encryption");
public Column<int> read_up_to = new Column.Integer("read_up_to");
public Column<int> notification = new Column.Integer("notification") { min_version=3 };
public Column<int> send_typing = new Column.Integer("send_typing") { min_version=3 };
public Column<int> send_marker = new Column.Integer("send_marker") { min_version=3 };
2017-03-02 14:37:32 +00:00
internal ConversationTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "conversation");
init({id, account_id, jid_id, resource, active, last_active, type_, encryption, read_up_to, notification, send_typing, send_marker});
2017-03-02 14:37:32 +00:00
}
}
public class AvatarTable : Table {
public Column<string> jid = new Column.Text("jid");
public Column<string> hash = new Column.Text("hash");
public Column<int> type_ = new Column.Integer("type");
internal AvatarTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "avatar");
init({jid, hash, type_});
}
}
public class EntityFeatureTable : Table {
public Column<string> entity = new Column.Text("entity");
public Column<string> feature = new Column.Text("feature");
internal EntityFeatureTable(Database db) {
2017-03-02 14:37:32 +00:00
base(db, "entity_feature");
init({entity, feature});
2017-04-23 08:23:11 +00:00
unique({entity, feature}, "IGNORE");
index("entity_feature_idx", {entity});
2017-03-02 14:37:32 +00:00
}
}
2017-05-21 21:30:30 +00:00
public class RosterTable : Table {
public Column<int> account_id = new Column.Integer("account_id");
public Column<string> jid = new Column.Text("jid");
public Column<string> handle = new Column.Text("name");
2017-05-21 21:30:30 +00:00
public Column<string> subscription = new Column.Text("subscription");
internal RosterTable(Database db) {
base(db, "roster");
init({account_id, jid, handle, subscription});
2017-05-21 21:30:30 +00:00
unique({account_id, jid}, "IGNORE");
}
}
2017-08-21 15:16:25 +00:00
public class SettingsTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<string> key = new Column.Text("key") { unique = true, not_null = true };
public Column<string> value = new Column.Text("value");
internal SettingsTable(Database db) {
base(db, "settings");
init({id, key, value});
}
}
2017-03-02 14:37:32 +00:00
public AccountTable account { get; private set; }
public JidTable jid { get; private set; }
public ContentTable content { get; private set; }
2017-03-02 14:37:32 +00:00
public MessageTable message { get; private set; }
public RealJidTable real_jid { get; private set; }
2017-08-29 22:03:37 +00:00
public FileTransferTable file_transfer { get; private set; }
2017-03-02 14:37:32 +00:00
public ConversationTable conversation { get; private set; }
public AvatarTable avatar { get; private set; }
public EntityFeatureTable entity_feature { get; private set; }
2017-05-21 21:30:30 +00:00
public RosterTable roster { get; private set; }
2017-08-21 15:16:25 +00:00
public SettingsTable settings { get; private set; }
2017-03-02 14:37:32 +00:00
public Map<int, string> jid_table_cache = new HashMap<int, string>();
public Map<string, int> jid_table_reverse = new HashMap<string, int>();
public Map<int, Account> account_table_cache = new HashMap<int, Account>();
2017-10-28 21:48:07 +00:00
public Database(string fileName) {
2017-03-02 14:37:32 +00:00
base(fileName, VERSION);
account = new AccountTable(this);
jid = new JidTable(this);
content = new ContentTable(this);
2017-03-02 14:37:32 +00:00
message = new MessageTable(this);
real_jid = new RealJidTable(this);
2017-08-29 22:03:37 +00:00
file_transfer = new FileTransferTable(this);
2017-03-02 14:37:32 +00:00
conversation = new ConversationTable(this);
avatar = new AvatarTable(this);
entity_feature = new EntityFeatureTable(this);
2017-05-21 21:30:30 +00:00
roster = new RosterTable(this);
2017-08-21 15:16:25 +00:00
settings = new SettingsTable(this);
init({ account, jid, content, message, real_jid, file_transfer, conversation, avatar, entity_feature, roster, settings });
2017-10-29 14:15:28 +00:00
try {
exec("PRAGMA synchronous=0");
} catch (Error e) { }
2017-03-02 14:37:32 +00:00
}
public override void migrate(long oldVersion) {
// new table columns are added, outdated columns are still present
2018-06-20 14:22:03 +00:00
if (oldVersion < 7) {
message.fts_rebuild();
} else if (oldVersion < 8) {
exec("""
insert into contentx (conversation_id, time, local_time, content_type, foreign_id)
select conversation.id, message.time, message.local_time, 1, message.id
from message join conversation on
message.account_id=conversation.account_id and
message.counterpart_id=conversation.jid_id and
message.type=conversation.type+1 and
(message.counterpart_resource=conversation.resource or message.type != 3)
where
message.body not in (select info from file_transfer where info not null) and
message.id not in (select info from file_transfer where info not null)
union
select conversation.id, message.time, message.local_time, 2, file_transfer.id
from file_transfer
join message on
file_transfer.info=message.id
join conversation on
file_transfer.account_id=conversation.account_id and
file_transfer.counterpart_id=conversation.jid_id and
message.type=conversation.type+1 and
(message.counterpart_resource=conversation.resource or message.type != 3)""");
2018-06-20 14:22:03 +00:00
}
2017-03-02 14:37:32 +00:00
}
public ArrayList<Account> get_accounts() {
ArrayList<Account> ret = new ArrayList<Account>();
foreach(Row row in account.select()) {
Account account = new Account.from_row(this, row);
2017-03-02 14:37:32 +00:00
ret.add(account);
account_table_cache[account.id] = account;
2017-03-02 14:37:32 +00:00
}
return ret;
}
public Account? get_account_by_id(int id) {
if (account_table_cache.has_key(id)) {
return account_table_cache[id];
} else {
Row? row = account.row_with(account.id, id).inner;
if (row != null) {
Account a = new Account.from_row(this, row);
account_table_cache[a.id] = a;
return a;
}
return null;
2017-03-02 14:37:32 +00:00
}
}
public int add_content_item(Conversation conversation, DateTime time, DateTime local_time, int content_type, int foreign_id) {
return (int) content.insert()
.value(content.conversation_id, conversation.id)
.value(content.local_time, (long) local_time.to_unix())
.value(content.time, (long) time.to_unix())
.value(content.content_type, content_type)
.value(content.foreign_id, foreign_id)
.perform();
}
public Gee.List<Message> get_messages(Xmpp.Jid jid, Account account, Message.Type? type, int count, DateTime? before, DateTime? after, int id) {
QueryBuilder select = message.select();
2017-03-02 14:37:32 +00:00
if (before != null) {
if (id > 0) {
select.where(@"local_time < ? OR (local_time = ? AND id < ?)", { before.to_unix().to_string(), before.to_unix().to_string(), id.to_string() });
} else {
select.with(message.id, "<", id);
}
}
if (after != null) {
if (id > 0) {
select.where(@"local_time > ? OR (local_time = ? AND id > ?)", { after.to_unix().to_string(), after.to_unix().to_string(), id.to_string() });
} else {
select.with(message.local_time, ">", (long) after.to_unix());
}
if (id > 0) {
select.with(message.id, ">", id);
}
} else {
select.order_by(message.id, "DESC");
2017-03-02 14:37:32 +00:00
}
select.with(message.counterpart_id, "=", get_jid_id(jid))
.with(message.account_id, "=", account.id)
.limit(count);
if (jid.resourcepart != null) {
select.with(message.counterpart_resource, "=", jid.resourcepart);
}
if (type != null) {
select.with(message.type_, "=", (int) type);
}
2017-03-02 14:37:32 +00:00
LinkedList<Message> ret = new LinkedList<Message>();
foreach (Row row in select) {
ret.insert(0, new Message.from_row(this, row));
2017-03-02 14:37:32 +00:00
}
return ret;
}
public Gee.List<Message> get_unsend_messages(Account account, Xmpp.Jid? jid = null) {
Gee.List<Message> ret = new ArrayList<Message>();
var select = message.select()
.with(message.account_id, "=", account.id)
.with(message.marked, "=", (int) Message.Marked.UNSENT);
2017-10-22 00:43:04 +00:00
if (jid != null) {
select.with(message.counterpart_id, "=", get_jid_id(jid));
}
foreach (Row row in select) {
ret.add(new Message.from_row(this, row));
}
return ret;
}
2017-03-02 14:37:32 +00:00
public bool contains_message(Message query_message, Account account) {
2017-03-17 15:21:07 +00:00
QueryBuilder builder = message.select()
2017-03-02 14:37:32 +00:00
.with(message.account_id, "=", account.id)
.with(message.counterpart_id, "=", get_jid_id(query_message.counterpart))
.with(message.body, "=", query_message.body)
.with(message.time, "<", (long) query_message.time.add_minutes(1).to_unix())
2017-03-17 15:21:07 +00:00
.with(message.time, ">", (long) query_message.time.add_minutes(-1).to_unix());
if (query_message.stanza_id != null) {
builder.with(message.stanza_id, "=", query_message.stanza_id);
} else {
builder.with_null(message.stanza_id);
}
if (query_message.counterpart.resourcepart != null) {
builder.with(message.counterpart_resource, "=", query_message.counterpart.resourcepart);
} else {
builder.with_null(message.counterpart_resource);
}
2017-03-17 15:21:07 +00:00
return builder.count() > 0;
2017-03-02 14:37:32 +00:00
}
public bool contains_message_by_stanza_id(Message query_message, Account account) {
QueryBuilder builder = message.select()
.with(message.stanza_id, "=", query_message.stanza_id)
.with(message.counterpart_id, "=", get_jid_id(query_message.counterpart))
.with(message.account_id, "=", account.id);
if (query_message.counterpart.resourcepart != null) {
builder.with(message.counterpart_resource, "=", query_message.counterpart.resourcepart);
} else {
builder.with_null(message.counterpart_resource);
}
return builder.count() > 0;
2017-03-02 14:37:32 +00:00
}
public Message? get_message_by_id(int id) {
Row? row = message.row_with(message.id, id).inner;
2017-03-02 14:37:32 +00:00
if (row != null) {
return new Message.from_row(this, row);
2017-03-02 14:37:32 +00:00
}
return null;
}
public ArrayList<Conversation> get_conversations(Account account) {
ArrayList<Conversation> ret = new ArrayList<Conversation>();
foreach (Row row in conversation.select().with(conversation.account_id, "=", account.id)) {
ret.add(new Conversation.from_row(this, row));
2017-03-02 14:37:32 +00:00
}
return ret;
}
public void set_avatar_hash(Xmpp.Jid jid, string hash, int type) {
2017-03-02 14:37:32 +00:00
avatar.insert().or("REPLACE")
.value(avatar.jid, jid.to_string())
.value(avatar.hash, hash)
.value(avatar.type_, type)
.perform();
}
public HashMap<Xmpp.Jid, string> get_avatar_hashes(int type) {
HashMap<Xmpp.Jid, string> ret = new HashMap<Xmpp.Jid, string>(Xmpp.Jid.hash_func, Xmpp.Jid.equals_func);
2017-03-02 14:37:32 +00:00
foreach (Row row in avatar.select({avatar.jid, avatar.hash}).with(avatar.type_, "=", type)) {
ret[Xmpp.Jid.parse(row[avatar.jid])] = row[avatar.hash];
2017-03-02 14:37:32 +00:00
}
return ret;
}
2017-08-02 15:29:55 +00:00
public void add_entity_features(string entity, Gee.List<string> features) {
2017-03-02 14:37:32 +00:00
foreach (string feature in features) {
entity_feature.insert()
.value(entity_feature.entity, entity)
.value(entity_feature.feature, feature)
.perform();
}
}
2017-08-02 15:29:55 +00:00
public Gee.List<string> get_entity_features(string entity) {
2017-03-02 14:37:32 +00:00
ArrayList<string> ret = new ArrayList<string>();
foreach (Row row in entity_feature.select({entity_feature.feature}).with(entity_feature.entity, "=", entity)) {
ret.add(row[entity_feature.feature]);
}
return ret;
}
public int get_jid_id(Xmpp.Jid jid_obj) {
string bare_jid = jid_obj.bare_jid.to_string();
if (jid_table_reverse.has_key(bare_jid)) {
return jid_table_reverse[bare_jid];
} else {
Row? row = jid.row_with(jid.bare_jid, jid_obj.bare_jid.to_string()).inner;
if (row != null) {
int id = row[jid.id];
jid_table_cache[id] = bare_jid;
jid_table_reverse[bare_jid] = id;
return id;
} else {
return add_jid(jid_obj);
}
}
2017-03-02 14:37:32 +00:00
}
public string? get_jid_by_id(int id) {
if (jid_table_cache.has_key(id)) {
return jid_table_cache[id];
} else {
string? bare_jid = jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid];
if (bare_jid != null) {
jid_table_cache[id] = bare_jid;
jid_table_reverse[bare_jid] = id;
}
return bare_jid;
}
2017-03-02 14:37:32 +00:00
}
private int add_jid(Xmpp.Jid jid_obj) {
string bare_jid = jid_obj.bare_jid.to_string();
int id = (int) jid.insert().value(jid.bare_jid, bare_jid).perform();
jid_table_cache[id] = bare_jid;
jid_table_reverse[bare_jid] = id;
return id;
2017-03-02 14:37:32 +00:00
}
}
}