Formatting, license header

This commit is contained in:
Adolfo Santiago 2022-01-15 20:36:53 +01:00
parent 237241baa1
commit 6d8aa8ff47
No known key found for this signature in database
GPG key ID: 244D6F9A317B4A65
2 changed files with 429 additions and 319 deletions

View file

@ -1,21 +1,53 @@
/* Copyright 2017 Andrew Dawson /*
* Husky -- A Pleroma client for Android
* *
* This file is a part of Tusky. * Copyright (C) 2021 The Husky Developers
* Copyright (C) 2017 Alibek "a1batross" Omarov
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * This program is free software: you can redistribute it and/or modify
* GNU General Public License as published by the Free Software Foundation; either version 3 of the * it under the terms of the GNU General Public License as published by
* License, or (at your option) any later version. * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * This program is distributed in the hope that it will be useful,
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * but WITHOUT ANY WARRANTY; without even the implied warranty of
* Public License for more details. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License
* see <http://www.gnu.org/licenses>. */ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.network package com.keylesspalace.tusky.network
import com.keylesspalace.tusky.entity.* import com.keylesspalace.tusky.entity.AccessToken
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Announcement
import com.keylesspalace.tusky.entity.AppCredentials
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Chat
import com.keylesspalace.tusky.entity.ChatMessage
import com.keylesspalace.tusky.entity.Conversation
import com.keylesspalace.tusky.entity.DeletedStatus
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.EmojiReaction
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.Marker
import com.keylesspalace.tusky.entity.MastoList
import com.keylesspalace.tusky.entity.NewChatMessage
import com.keylesspalace.tusky.entity.NewStatus
import com.keylesspalace.tusky.entity.NodeInfo
import com.keylesspalace.tusky.entity.NodeInfoLinks
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Relationship
import com.keylesspalace.tusky.entity.ScheduledStatus
import com.keylesspalace.tusky.entity.SearchResult
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.entity.StatusContext
import com.keylesspalace.tusky.entity.StickerPack
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import okhttp3.MultipartBody import okhttp3.MultipartBody
@ -23,8 +55,21 @@ import okhttp3.RequestBody
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.Call import retrofit2.Call
import retrofit2.Response import retrofit2.Response
import retrofit2.http.* import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.HTTP
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Url
/** /**
* for documentation of the Mastodon REST API see https://docs.joinmastodon.org/api/ * for documentation of the Mastodon REST API see https://docs.joinmastodon.org/api/
@ -585,10 +630,10 @@ interface MastodonApi {
): Single<SearchResult> ): Single<SearchResult>
@GET(".well-known/nodeinfo") @GET(".well-known/nodeinfo")
fun getNodeinfoLinks() : Single<NodeInfoLinks> fun getNodeinfoLinks(): Single<NodeInfoLinks>
@GET @GET
fun getNodeinfo(@Url url: String) : Single<NodeInfo> fun getNodeinfo(@Url url: String): Single<NodeInfo>
@PUT("api/v1/pleroma/statuses/{id}/reactions/{emoji}") @PUT("api/v1/pleroma/statuses/{id}/reactions/{emoji}")
fun reactWithEmoji( fun reactWithEmoji(
@ -611,7 +656,7 @@ interface MastodonApi {
// NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS // NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS
// just for testing and because puniko asked me // just for testing and because puniko asked me
@GET("static/stickers.json") @GET("static/stickers.json")
fun getStickers() : Single<Map<String, String>> fun getStickers(): Single<Map<String, String>>
@GET @GET
fun getStickerPack( fun getStickerPack(

View file

@ -1,3 +1,23 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
* Copyright (C) 2021 Andrew Dawson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.repository package com.keylesspalace.tusky.repository
import android.text.SpannedString import android.text.SpannedString
@ -5,8 +25,16 @@ import androidx.core.text.parseAsHtml
import androidx.core.text.toHtml import androidx.core.text.toHtml
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.keylesspalace.tusky.db.* import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.* import com.keylesspalace.tusky.db.ChatEntity
import com.keylesspalace.tusky.db.ChatEntityWithAccount
import com.keylesspalace.tusky.db.ChatMessageEntity
import com.keylesspalace.tusky.db.ChatsDao
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Chat
import com.keylesspalace.tusky.entity.ChatMessage
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK
import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK
@ -17,16 +45,25 @@ import com.keylesspalace.tusky.util.trimTrailingWhitespace
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import java.io.IOException import java.io.IOException
import java.util.* import java.util.Date
typealias ChatStatus = Either<Placeholder, Chat> typealias ChatStatus = Either<Placeholder, Chat>
typealias ChatMesssageOrPlaceholder = Either<Placeholder, ChatMessage> typealias ChatMesssageOrPlaceholder = Either<Placeholder, ChatMessage>
interface ChatRepository { interface ChatRepository {
fun getChats(maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, fun getChats(
requestMode: TimelineRequestMode): Single<out List<ChatStatus>> maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int,
requestMode: TimelineRequestMode
): Single<out List<ChatStatus>>
fun getChatMessages(chatId: String, maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, requestMode: TimelineRequestMode) : Single<out List<ChatMesssageOrPlaceholder>> fun getChatMessages(
chatId: String,
maxId: String?,
sinceId: String?,
sincedIdMinusOne: String?,
limit: Int,
requestMode: TimelineRequestMode
): Single<out List<ChatMesssageOrPlaceholder>>
} }
class ChatRepositoryImpl( class ChatRepositoryImpl(
@ -36,20 +73,28 @@ class ChatRepositoryImpl(
private val gson: Gson private val gson: Gson
) : ChatRepository { ) : ChatRepository {
override fun getChats(maxId: String?, sinceId: String?, sincedIdMinusOne: String?, override fun getChats(
maxId: String?, sinceId: String?, sincedIdMinusOne: String?,
limit: Int, requestMode: TimelineRequestMode limit: Int, requestMode: TimelineRequestMode
): Single<out List<ChatStatus>> { ): Single<out List<ChatStatus>> {
val acc = accountManager.activeAccount ?: throw IllegalStateException() val acc = accountManager.activeAccount ?: throw IllegalStateException()
val accountId = acc.id val accountId = acc.id
return if (requestMode == DISK) { return if(requestMode == DISK) {
this.getChatsFromDb(accountId, maxId, sinceId, limit) this.getChatsFromDb(accountId, maxId, sinceId, limit)
} else { } else {
getChatsFromNetwork(maxId, sinceId, sincedIdMinusOne, limit, accountId, requestMode) getChatsFromNetwork(maxId, sinceId, sincedIdMinusOne, limit, accountId, requestMode)
} }
} }
override fun getChatMessages(chatId: String, maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, requestMode: TimelineRequestMode) : Single<out List<ChatMesssageOrPlaceholder>> { override fun getChatMessages(
chatId: String,
maxId: String?,
sinceId: String?,
sincedIdMinusOne: String?,
limit: Int,
requestMode: TimelineRequestMode
): Single<out List<ChatMesssageOrPlaceholder>> {
val acc = accountManager.activeAccount ?: throw IllegalStateException() val acc = accountManager.activeAccount ?: throw IllegalStateException()
val accountId = acc.id val accountId = acc.id
@ -62,7 +107,8 @@ class ChatRepositoryImpl(
return getChatMessagesFromNetwork(chatId, maxId, null, null, limit, accountId, requestMode) return getChatMessagesFromNetwork(chatId, maxId, null, null, limit, accountId, requestMode)
} }
private fun getChatsFromNetwork(maxId: String?, sinceId: String?, private fun getChatsFromNetwork(
maxId: String?, sinceId: String?,
sinceIdMinusOne: String?, limit: Int, sinceIdMinusOne: String?, limit: Int,
accountId: Long, requestMode: TimelineRequestMode accountId: Long, requestMode: TimelineRequestMode
): Single<out List<ChatStatus>> { ): Single<out List<ChatStatus>> {
@ -74,7 +120,7 @@ class ChatRepositoryImpl(
this.addFromDbIfNeeded(accountId, chats, maxId, sinceId, limit, requestMode) this.addFromDbIfNeeded(accountId, chats, maxId, sinceId, limit, requestMode)
} }
.onErrorResumeNext { error -> .onErrorResumeNext { error ->
if (error is IOException && requestMode != NETWORK) { if(error is IOException && requestMode != NETWORK) {
this.getChatsFromDb(accountId, maxId, sinceId, limit) this.getChatsFromDb(accountId, maxId, sinceId, limit)
} else { } else {
Single.error(error) Single.error(error)
@ -82,7 +128,8 @@ class ChatRepositoryImpl(
} }
} }
private fun getChatMessagesFromNetwork(chatId: String, maxId: String?, sinceId: String?, private fun getChatMessagesFromNetwork(
chatId: String, maxId: String?, sinceId: String?,
sinceIdMinusOne: String?, limit: Int, sinceIdMinusOne: String?, limit: Int,
accountId: Long, requestMode: TimelineRequestMode accountId: Long, requestMode: TimelineRequestMode
): Single<out List<ChatMesssageOrPlaceholder>> { ): Single<out List<ChatMesssageOrPlaceholder>> {
@ -92,12 +139,13 @@ class ChatRepositoryImpl(
} }
private fun addFromDbIfNeeded(accountId: Long, chats: List<ChatStatus>, private fun addFromDbIfNeeded(
accountId: Long, chats: List<ChatStatus>,
maxId: String?, sinceId: String?, limit: Int, maxId: String?, sinceId: String?, limit: Int,
requestMode: TimelineRequestMode requestMode: TimelineRequestMode
): Single<List<ChatStatus>> { ): Single<List<ChatStatus>> {
return if (requestMode != NETWORK && chats.size < 2) { return if(requestMode != NETWORK && chats.size < 2) {
val newMaxID = if (chats.isEmpty()) { val newMaxID = if(chats.isEmpty()) {
maxId maxId
} else { } else {
chats.last { it.isRight() }.asRight().id chats.last { it.isRight() }.asRight().id
@ -106,7 +154,7 @@ class ChatRepositoryImpl(
.map { fromDb -> .map { fromDb ->
// If it's just placeholders and less than limit (so we exhausted both // If it's just placeholders and less than limit (so we exhausted both
// db and server at this point) // db and server at this point)
if (fromDb.size < limit && fromDb.all { !it.isRight() }) { if(fromDb.size < limit && fromDb.all { !it.isRight() }) {
chats chats
} else { } else {
chats + fromDb chats + fromDb
@ -117,8 +165,10 @@ class ChatRepositoryImpl(
} }
} }
private fun getChatsFromDb(accountId: Long, maxId: String?, sinceId: String?, private fun getChatsFromDb(
limit: Int): Single<out List<ChatStatus>> { accountId: Long, maxId: String?, sinceId: String?,
limit: Int
): Single<out List<ChatStatus>> {
return chatsDao.getChatsForAccount(accountId, maxId, sinceId, limit) return chatsDao.getChatsForAccount(accountId, maxId, sinceId, limit)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.map { chats -> .map { chats ->
@ -127,15 +177,16 @@ class ChatRepositoryImpl(
} }
private fun saveChatsToDb(accountId: Long, chats: List<Chat>, private fun saveChatsToDb(
accountId: Long, chats: List<Chat>,
maxId: String?, sinceId: String? maxId: String?, sinceId: String?
): List<ChatStatus> { ): List<ChatStatus> {
var placeholderToInsert: Placeholder? = null var placeholderToInsert: Placeholder? = null
// Look for overlap // Look for overlap
val resultChats = if (chats.isNotEmpty() && sinceId != null) { val resultChats = if(chats.isNotEmpty() && sinceId != null) {
val indexOfSince = chats.indexOfLast { it.id == sinceId } val indexOfSince = chats.indexOfLast { it.id == sinceId }
if (indexOfSince == -1) { if(indexOfSince == -1) {
// We didn't find the status which must be there. Add a placeholder // We didn't find the status which must be there. Add a placeholder
placeholderToInsert = Placeholder(sinceId.inc()) placeholderToInsert = Placeholder(sinceId.inc())
chats.mapTo(mutableListOf(), Chat::lift) chats.mapTo(mutableListOf(), Chat::lift)
@ -160,7 +211,7 @@ class ChatRepositoryImpl(
chatsDao.deleteRange(accountId, chats.last().id, chats.first().id) chatsDao.deleteRange(accountId, chats.last().id, chats.first().id)
} }
for (chat in chats) { for(chat in chats) {
val pair = chat.toEntity(accountId, gson) val pair = chat.toEntity(accountId, gson)
chatsDao.insertInTransaction( chatsDao.insertInTransaction(
@ -176,16 +227,19 @@ class ChatRepositoryImpl(
// If we're loading in the bottom insert placeholder after every load // If we're loading in the bottom insert placeholder after every load
// (for requests on next launches) but not return it. // (for requests on next launches) but not return it.
if (sinceId == null && chats.isNotEmpty()) { if(sinceId == null && chats.isNotEmpty()) {
chatsDao.insertChatIfNotThere( chatsDao.insertChatIfNotThere(
Placeholder(chats.last().id.dec()).toChatEntity(accountId)) Placeholder(chats.last().id.dec()).toChatEntity(accountId)
)
} }
// There may be placeholders which we thought could be from our TL but they are not // There may be placeholders which we thought could be from our TL but they are not
if (chats.size > 2) { if(chats.size > 2) {
chatsDao.removeAllPlaceholdersBetween(accountId, chats.first().id, chatsDao.removeAllPlaceholdersBetween(
chats.last().id) accountId, chats.first().id,
} else if (placeholderToInsert == null && maxId != null && sinceId != null) { chats.last().id
)
} else if(placeholderToInsert == null && maxId != null && sinceId != null) {
chatsDao.removeAllPlaceholdersBetween(accountId, maxId, sinceId) chatsDao.removeAllPlaceholdersBetween(accountId, maxId, sinceId)
} }
} }
@ -209,7 +263,7 @@ fun Placeholder.toChatEntity(timelineUserId: Long): ChatEntity {
) )
} }
fun ChatMessage.toEntity(timelineUserId: Long, gson: Gson) : ChatMessageEntity { fun ChatMessage.toEntity(timelineUserId: Long, gson: Gson): ChatMessageEntity {
return ChatMessageEntity( return ChatMessageEntity(
localId = timelineUserId, localId = timelineUserId,
messageId = this.id, messageId = this.id,
@ -223,17 +277,19 @@ fun ChatMessage.toEntity(timelineUserId: Long, gson: Gson) : ChatMessageEntity {
} }
fun Chat.toEntity(timelineUserId: Long, gson: Gson): Pair<ChatEntity, ChatMessageEntity?> { fun Chat.toEntity(timelineUserId: Long, gson: Gson): Pair<ChatEntity, ChatMessageEntity?> {
return Pair(ChatEntity( return Pair(
ChatEntity(
localId = timelineUserId, localId = timelineUserId,
chatId = this.id, chatId = this.id,
accountId = this.account.id, accountId = this.account.id,
unread = this.unread, unread = this.unread,
updatedAt = this.updatedAt.time, updatedAt = this.updatedAt.time,
lastMessageId = this.lastMessage?.id lastMessageId = this.lastMessage?.id
), this.lastMessage?.toEntity(timelineUserId, gson)) ), this.lastMessage?.toEntity(timelineUserId, gson)
)
} }
fun ChatMessageEntity.toChatMessage(gson: Gson) : ChatMessage { fun ChatMessageEntity.toChatMessage(gson: Gson): ChatMessage {
return ChatMessage( return ChatMessage(
id = this.messageId, id = this.messageId,
content = this.content?.let { it.parseAsHtml().trimTrailingWhitespace() }, content = this.content?.let { it.parseAsHtml().trimTrailingWhitespace() },
@ -241,17 +297,26 @@ fun ChatMessageEntity.toChatMessage(gson: Gson) : ChatMessage {
accountId = this.accountId, accountId = this.accountId,
createdAt = Date(this.createdAt), createdAt = Date(this.createdAt),
attachment = this.attachment?.let { gson.fromJson(it, Attachment::class.java) }, attachment = this.attachment?.let { gson.fromJson(it, Attachment::class.java) },
emojis = gson.fromJson(this.emojis, object : TypeToken<List<Emoji>>() {}.type ), emojis = gson.fromJson(this.emojis, object : TypeToken<List<Emoji>>() {}.type),
card = null /* don't care about card */ card = null /* don't care about card */
) )
} }
fun ChatEntityWithAccount.toChat(gson: Gson) : ChatStatus { fun ChatEntityWithAccount.toChat(gson: Gson): ChatStatus {
if(account == null || chat.accountId.isEmpty() || chat.updatedAt == 0L) if(account == null || chat.accountId.isEmpty() || chat.updatedAt == 0L)
return Either.Left(Placeholder(chat.chatId)) return Either.Left(Placeholder(chat.chatId))
return Chat( return Chat(
account = this.account?.toAccount(gson) ?: Account("", "", "", "", SpannedString(""), "", "", "" ), account = this.account?.toAccount(gson) ?: Account(
"",
"",
"",
"",
SpannedString(""),
"",
"",
""
),
id = this.chat.chatId, id = this.chat.chatId,
unread = this.chat.unread, unread = this.chat.unread,
updatedAt = Date(this.chat.updatedAt), updatedAt = Date(this.chat.updatedAt),