mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e875d4632 | |||
| 4cfda91124 | |||
| 7aec147cec | |||
| f6f2044675 | |||
| 5f0b5c5a9f |
@@ -9,171 +9,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SimpleXChat
|
import SimpleXChat
|
||||||
|
|
||||||
struct NewUserProfile: View {
|
|
||||||
@State private var profile = Profile(displayName: "", fullName: "")
|
|
||||||
// Modals
|
|
||||||
@State private var showChooseSource = false
|
|
||||||
@State private var showImagePicker = false
|
|
||||||
@State private var showFilePicker = false
|
|
||||||
@State private var showTakePhoto = false
|
|
||||||
@State private var chosenImage: UIImage? = nil
|
|
||||||
@State private var alert: UserProfileAlert?
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List {
|
|
||||||
Section {
|
|
||||||
Text("""
|
|
||||||
Your profile is stored on your device and shared only with your contacts.
|
|
||||||
SimpleX servers cannot see your profile.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Section {
|
|
||||||
ProfileImage(imageStr: profile.image, size: 128)
|
|
||||||
.padding(8)
|
|
||||||
.overlay {
|
|
||||||
if profile.image != nil {
|
|
||||||
overlayButton("xmark", color: .red, alignment: .topTrailing) { profile.image = nil }
|
|
||||||
}
|
|
||||||
overlayButton("pencil", color: .accentColor, alignment: .bottomTrailing) {
|
|
||||||
showChooseSource = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
|
||||||
if showFullName {
|
|
||||||
nameField("Full name", text: $profile.fullName)
|
|
||||||
}
|
|
||||||
nameField("Profile name", text: $profile.displayName)
|
|
||||||
Button("Save and notify contacts", action: saveProfile).disabled(!canSaveProfile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Lifecycle
|
|
||||||
.task {
|
|
||||||
if let user = ChatModel.shared.currentUser {
|
|
||||||
profile = fromLocalProfile(user.profile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: chosenImage) { image in
|
|
||||||
if let image {
|
|
||||||
profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500)
|
|
||||||
} else {
|
|
||||||
profile.image = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Modals
|
|
||||||
.confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) {
|
|
||||||
Button("Take picture") {
|
|
||||||
showTakePhoto = true
|
|
||||||
}
|
|
||||||
Button("Choose from library") {
|
|
||||||
showImagePicker = true
|
|
||||||
}
|
|
||||||
Button("Choose file") {
|
|
||||||
showFilePicker = true
|
|
||||||
}
|
|
||||||
if UIPasteboard.general.hasImages {
|
|
||||||
Button("Paste image") {
|
|
||||||
chosenImage = UIPasteboard.general.image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fullScreenCover(isPresented: $showTakePhoto) {
|
|
||||||
ZStack {
|
|
||||||
Color.black.edgesIgnoringSafeArea(.all)
|
|
||||||
CameraImagePicker(image: $chosenImage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $showImagePicker) {
|
|
||||||
LibraryImagePicker(image: $chosenImage) { _ in
|
|
||||||
await MainActor.run {
|
|
||||||
showImagePicker = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fileImporter(isPresented: $showFilePicker, allowedContentTypes: [.image]) { url in
|
|
||||||
|
|
||||||
}
|
|
||||||
.alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private func overlayButton(
|
|
||||||
_ systemName: String,
|
|
||||||
color: Color,
|
|
||||||
alignment: Alignment,
|
|
||||||
action: @escaping () -> Void
|
|
||||||
) -> some View {
|
|
||||||
Image(systemName: systemName)
|
|
||||||
.foregroundStyle(color)
|
|
||||||
.imageScale(.large)
|
|
||||||
.padding(8)
|
|
||||||
.background(.bar)
|
|
||||||
.clipShape(Circle())
|
|
||||||
.onTapGesture(perform: action)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private func nameField(
|
|
||||||
_ title: LocalizedStringKey,
|
|
||||||
text: Binding<String>
|
|
||||||
) -> some View {
|
|
||||||
let isValid = validDisplayName(text.wrappedValue)
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
HStack {
|
|
||||||
Text(title).foregroundStyle(.secondary).font(.caption)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "exclamationmark.circle")
|
|
||||||
.foregroundColor(.red)
|
|
||||||
.opacity(isValid ? 0 : 1)
|
|
||||||
.onTapGesture {
|
|
||||||
alert = .invalidNameError(validName: mkValidName(profile.displayName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TextField(title, text: text)
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
.padding(.horizontal, 4)
|
|
||||||
.overlay {
|
|
||||||
RoundedRectangle(cornerRadius: 6, style: .continuous)
|
|
||||||
.stroke(isValid ? Color(.tertiaryLabel) : Color.red)
|
|
||||||
}
|
|
||||||
}.listRowSeparator(.hidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Computed
|
|
||||||
private func validNewProfileName(_ user: User) -> Bool {
|
|
||||||
profile.displayName == user.profile.displayName || validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces))
|
|
||||||
}
|
|
||||||
|
|
||||||
private var showFullName: Bool {
|
|
||||||
profile.fullName != "" &&
|
|
||||||
profile.fullName != profile.displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
private var canSaveProfile: Bool {
|
|
||||||
profile.displayName.trimmingCharacters(in: .whitespaces) != "" &&
|
|
||||||
validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func saveProfile() {
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
|
|
||||||
if let (newProfile, _) = try await apiUpdateProfile(profile: profile) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
ChatModel.shared.updateCurrentUser(newProfile)
|
|
||||||
profile = newProfile
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert = .duplicateUserError
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
logger.error("UserProfile apiUpdateProfile error: \(responseError(error))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UserProfile: View {
|
struct UserProfile: View {
|
||||||
@EnvironmentObject var chatModel: ChatModel
|
@EnvironmentObject var chatModel: ChatModel
|
||||||
@State private var profile = Profile(displayName: "", fullName: "")
|
@State private var profile = Profile(displayName: "", fullName: "")
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "codescanner",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/twostraws/CodeScanner",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "34da57fb63b47add20de8a85da58191523ccce57",
|
||||||
|
"version" : "2.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "lzstring-swift",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/Ibrahimhass/lzstring-swift",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "7f62f21de5b18582a950e1753b775cc614722407"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftygif",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kirualex/SwiftyGif",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "5e8619335d394901379c9add5c4c1c2f420b3800"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "webrtc",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/simplex-chat/WebRTC.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "34bedc50f9c58dccf4967ea59c7e6a47d620803b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "yams",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/jpsim/Yams",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76",
|
||||||
|
"version" : "5.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
@@ -32,7 +32,6 @@ android {
|
|||||||
}
|
}
|
||||||
manifestPlaceholders["app_name"] = "@string/app_name"
|
manifestPlaceholders["app_name"] = "@string/app_name"
|
||||||
manifestPlaceholders["provider_authorities"] = "chat.simplex.app.provider"
|
manifestPlaceholders["provider_authorities"] = "chat.simplex.app.provider"
|
||||||
manifestPlaceholders["extract_native_libs"] = rootProject.extra["compression.level"] as Int != 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -75,7 +74,6 @@ android {
|
|||||||
resources {
|
resources {
|
||||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
}
|
}
|
||||||
jniLibs.useLegacyPackaging = rootProject.extra["compression.level"] as Int != 0
|
|
||||||
}
|
}
|
||||||
android.sourceSets["main"].assets.setSrcDirs(listOf("../common/src/commonMain/resources/assets"))
|
android.sourceSets["main"].assets.setSrcDirs(listOf("../common/src/commonMain/resources/assets"))
|
||||||
val isRelease = gradle.startParameter.taskNames.find { it.lowercase().contains("release") } != null
|
val isRelease = gradle.startParameter.taskNames.find { it.lowercase().contains("release") } != null
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
android:fullBackupOnly="false"
|
android:fullBackupOnly="false"
|
||||||
android:icon="@mipmap/icon"
|
android:icon="@mipmap/icon"
|
||||||
android:label="${app_name}"
|
android:label="${app_name}"
|
||||||
android:extractNativeLibs="${extract_native_libs}"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.SimpleX">
|
android:theme="@style/Theme.SimpleX">
|
||||||
<!-- android:localeConfig="@xml/locales_config"-->
|
<!-- android:localeConfig="@xml/locales_config"-->
|
||||||
|
|||||||
@@ -99,10 +99,15 @@ kotlin {
|
|||||||
val desktopMain by getting {
|
val desktopMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0")
|
||||||
implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8")
|
implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") {
|
||||||
|
exclude("net.java.dev.jna")
|
||||||
|
}
|
||||||
|
// For jSystemThemeDetector only
|
||||||
|
implementation("net.java.dev.jna:jna-platform:5.14.0")
|
||||||
implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT")
|
implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT")
|
||||||
implementation("org.slf4j:slf4j-simple:2.0.12")
|
implementation("org.slf4j:slf4j-simple:2.0.12")
|
||||||
implementation("uk.co.caprica:vlcj:4.8.3")
|
implementation("uk.co.caprica:vlcj:4.8.3")
|
||||||
|
implementation("net.java.dev.jna:jna:5.14.0")
|
||||||
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a")
|
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a")
|
||||||
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a")
|
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a")
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
|||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/simplexmq.git
|
location: https://github.com/simplex-chat/simplexmq.git
|
||||||
tag: 946e16339e16e026f51185ebfb48c3a0c5a5b2e1
|
tag: f5e666ae4f41351d5d5ac416cd6fb1d5fadc8ab7
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ for ORIG_NAME in "${ORIG_NAMES[@]}"; do
|
|||||||
(cd apk && zip -r -q -"$level" ../"$ORIG_NAME" .)
|
(cd apk && zip -r -q -"$level" ../"$ORIG_NAME" .)
|
||||||
# Shouldn't be compressed because of Android requirement
|
# Shouldn't be compressed because of Android requirement
|
||||||
(cd apk && zip -r -q -0 ../"$ORIG_NAME" resources.arsc)
|
(cd apk && zip -r -q -0 ../"$ORIG_NAME" resources.arsc)
|
||||||
|
(cd apk && zip -r -q -0 ../"$ORIG_NAME" lib/**/*.so)
|
||||||
|
|
||||||
if [ $case_insensitive -eq 1 ]; then
|
if [ $case_insensitive -eq 1 ]; then
|
||||||
# For case-insensitive file systems
|
# For case-insensitive file systems
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."946e16339e16e026f51185ebfb48c3a0c5a5b2e1" = "1jkx2f14h5krmy467zyifsc58dys89pkpn08cyf1q9v78in7nwfd";
|
"https://github.com/simplex-chat/simplexmq.git"."f5e666ae4f41351d5d5ac416cd6fb1d5fadc8ab7" = "1cq9apm9vp40v4ck0wcbis4463q3cjd9fbx5511hhh6lah6llifc";
|
||||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||||
|
|||||||
+120
-72
@@ -843,9 +843,7 @@ processChatCommand' vr = \case
|
|||||||
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
||||||
APIDeleteChatItem (ChatRef cType chatId) itemIds mode -> withUser $ \user -> case cType of
|
APIDeleteChatItem (ChatRef cType chatId) itemIds mode -> withUser $ \user -> case cType of
|
||||||
CTDirect -> withContactLock "deleteChatItem" chatId $ do
|
CTDirect -> withContactLock "deleteChatItem" chatId $ do
|
||||||
ct <- withStore $ \db -> getContact db vr user chatId
|
(ct, items) <- getCommandDirectChatItems user chatId itemIds
|
||||||
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds))
|
|
||||||
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
|
||||||
case mode of
|
case mode of
|
||||||
CIDMInternal -> deleteDirectCIs user ct items True False
|
CIDMInternal -> deleteDirectCIs user ct items True False
|
||||||
CIDMBroadcast -> do
|
CIDMBroadcast -> do
|
||||||
@@ -858,13 +856,9 @@ processChatCommand' vr = \case
|
|||||||
if featureAllowed SCFFullDelete forUser ct
|
if featureAllowed SCFFullDelete forUser ct
|
||||||
then deleteDirectCIs user ct items True False
|
then deleteDirectCIs user ct items True False
|
||||||
else markDirectCIsDeleted user ct items True =<< liftIO getCurrentTime
|
else markDirectCIsDeleted user ct items True =<< liftIO getCurrentTime
|
||||||
where
|
|
||||||
getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect))
|
|
||||||
getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user chatId itemId
|
|
||||||
CTGroup -> withGroupLock "deleteChatItem" chatId $ do
|
CTGroup -> withGroupLock "deleteChatItem" chatId $ do
|
||||||
Group gInfo ms <- withStore $ \db -> getGroup db vr user chatId
|
(gInfo, items) <- getCommandGroupChatItems user chatId itemIds
|
||||||
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds))
|
ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
|
||||||
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
|
||||||
case mode of
|
case mode of
|
||||||
CIDMInternal -> deleteGroupCIs user gInfo items True False Nothing =<< liftIO getCurrentTime
|
CIDMInternal -> deleteGroupCIs user gInfo items True False Nothing =<< liftIO getCurrentTime
|
||||||
CIDMBroadcast -> do
|
CIDMBroadcast -> do
|
||||||
@@ -874,17 +868,9 @@ processChatCommand' vr = \case
|
|||||||
events = L.nonEmpty $ map (`XMsgDel` Nothing) msgIds
|
events = L.nonEmpty $ map (`XMsgDel` Nothing) msgIds
|
||||||
mapM_ (sendGroupMessages user gInfo ms) events
|
mapM_ (sendGroupMessages user gInfo ms) events
|
||||||
delGroupChatItems user gInfo items Nothing
|
delGroupChatItems user gInfo items Nothing
|
||||||
where
|
|
||||||
getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup))
|
|
||||||
getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user chatId itemId
|
|
||||||
CTLocal -> do
|
CTLocal -> do
|
||||||
nf <- withStore $ \db -> getNoteFolder db user chatId
|
(nf, items) <- getCommandLocalChatItems user chatId itemIds
|
||||||
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds))
|
|
||||||
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
|
||||||
deleteLocalCIs user nf items True False
|
deleteLocalCIs user nf items True False
|
||||||
where
|
|
||||||
getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal))
|
|
||||||
getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user chatId itemId
|
|
||||||
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
|
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
|
||||||
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
||||||
where
|
where
|
||||||
@@ -902,9 +888,8 @@ processChatCommand' vr = \case
|
|||||||
itemsMsgIds :: [CChatItem c] -> [SharedMsgId]
|
itemsMsgIds :: [CChatItem c] -> [SharedMsgId]
|
||||||
itemsMsgIds = mapMaybe (\(CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId}}) -> itemSharedMsgId)
|
itemsMsgIds = mapMaybe (\(CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId}}) -> itemSharedMsgId)
|
||||||
APIDeleteMemberChatItem gId itemIds -> withUser $ \user -> withGroupLock "deleteChatItem" gId $ do
|
APIDeleteMemberChatItem gId itemIds -> withUser $ \user -> withGroupLock "deleteChatItem" gId $ do
|
||||||
Group gInfo@GroupInfo {membership} ms <- withStore $ \db -> getGroup db vr user gId
|
(gInfo@GroupInfo {membership}, items) <- getCommandGroupChatItems user gId itemIds
|
||||||
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db user) (L.toList itemIds))
|
ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
|
||||||
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
|
||||||
assertDeletable gInfo items
|
assertDeletable gInfo items
|
||||||
assertUserGroupRole gInfo GRAdmin
|
assertUserGroupRole gInfo GRAdmin
|
||||||
let msgMemIds = itemsMsgMemIds gInfo items
|
let msgMemIds = itemsMsgMemIds gInfo items
|
||||||
@@ -912,8 +897,6 @@ processChatCommand' vr = \case
|
|||||||
mapM_ (sendGroupMessages user gInfo ms) events
|
mapM_ (sendGroupMessages user gInfo ms) events
|
||||||
delGroupChatItems user gInfo items (Just membership)
|
delGroupChatItems user gInfo items (Just membership)
|
||||||
where
|
where
|
||||||
getGroupCI :: DB.Connection -> User -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup))
|
|
||||||
getGroupCI db user itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user gId itemId
|
|
||||||
assertDeletable :: GroupInfo -> [CChatItem 'CTGroup] -> CM ()
|
assertDeletable :: GroupInfo -> [CChatItem 'CTGroup] -> CM ()
|
||||||
assertDeletable GroupInfo {membership = GroupMember {memberRole = membershipMemRole}} items =
|
assertDeletable GroupInfo {membership = GroupMember {memberRole = membershipMemRole}} items =
|
||||||
unless (all itemDeletable items) $ throwChatError CEInvalidChatItemDelete
|
unless (all itemDeletable items) $ throwChatError CEInvalidChatItemDelete
|
||||||
@@ -980,6 +963,51 @@ processChatCommand' vr = \case
|
|||||||
throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed")
|
throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed")
|
||||||
when (add && length rs >= maxMsgReactions) $
|
when (add && length rs >= maxMsgReactions) $
|
||||||
throwChatError (CECommandError "too many reactions")
|
throwChatError (CECommandError "too many reactions")
|
||||||
|
APIPlanForwardChatItems (ChatRef fromCType fromChatId) itemIds -> withUser $ \user -> case fromCType of
|
||||||
|
CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds
|
||||||
|
CTGroup -> planForward user . snd =<< getCommandGroupChatItems user fromChatId itemIds
|
||||||
|
CTLocal -> planForward user . snd =<< getCommandLocalChatItems user fromChatId itemIds
|
||||||
|
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
|
||||||
|
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
||||||
|
where
|
||||||
|
planForward :: User -> [CChatItem c] -> CM ChatResponse
|
||||||
|
planForward user items = do
|
||||||
|
(itemIds', forwardErrors) <- unzip <$> mapM planItemForward items
|
||||||
|
let forwardConfirmation = case catMaybes forwardErrors of
|
||||||
|
[] -> Nothing
|
||||||
|
errs -> Just $ case mainErr of
|
||||||
|
FFENotAccepted _ -> FCFilesNotAccepted fileIds
|
||||||
|
FFEInProgress -> FCFilesInProgress filesCount
|
||||||
|
FFEMissing -> FCFilesMissing filesCount
|
||||||
|
FFEFailed -> FCFilesFailed filesCount
|
||||||
|
where
|
||||||
|
mainErr = minimum errs
|
||||||
|
fileIds = catMaybes $ map (\case FFENotAccepted ftId -> Just ftId; _ -> Nothing) errs
|
||||||
|
filesCount = length $ filter (mainErr ==) errs
|
||||||
|
pure CRForwardPlan {user, itemsCount = length itemIds, chatItemIds = catMaybes itemIds', forwardConfirmation}
|
||||||
|
where
|
||||||
|
planItemForward :: CChatItem c -> CM (Maybe ChatItemId, Maybe ForwardFileError)
|
||||||
|
planItemForward (CChatItem _ ci) = forwardMsgContent ci >>= maybe (pure (Nothing, Nothing)) (forwardContentPlan ci)
|
||||||
|
forwardContentPlan :: ChatItem c d -> MsgContent -> CM (Maybe ChatItemId, Maybe ForwardFileError)
|
||||||
|
forwardContentPlan ChatItem {file, meta = CIMeta {itemId}} mc = case file of
|
||||||
|
Nothing -> pure (Just itemId, Nothing)
|
||||||
|
Just CIFile {fileId, fileStatus, fileSource} -> case ciFileForwardError fileId fileStatus of
|
||||||
|
Just err -> pure $ itemIdWithoutFile err
|
||||||
|
Nothing -> case fileSource of
|
||||||
|
Just CryptoFile {filePath} -> do
|
||||||
|
exists <- doesFileExist . maybe filePath (</> filePath) =<< chatReadVar filesFolder
|
||||||
|
pure $ if exists then (Just itemId, Nothing) else itemIdWithoutFile FFEMissing
|
||||||
|
Nothing -> pure $ itemIdWithoutFile FFEMissing
|
||||||
|
where
|
||||||
|
itemIdWithoutFile err = (if hasContent then Just itemId else Nothing, Just err)
|
||||||
|
hasContent = case mc of
|
||||||
|
MCText _ -> True
|
||||||
|
MCLink {} -> True
|
||||||
|
MCImage {} -> True
|
||||||
|
MCVideo {text} -> text /= ""
|
||||||
|
MCVoice {text} -> text /= ""
|
||||||
|
MCFile t -> t /= ""
|
||||||
|
MCUnknown {} -> True
|
||||||
APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of
|
APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of
|
||||||
CTDirect -> do
|
CTDirect -> do
|
||||||
cmrs <- prepareForward user
|
cmrs <- prepareForward user
|
||||||
@@ -987,96 +1015,76 @@ processChatCommand' vr = \case
|
|||||||
Just cmrs' ->
|
Just cmrs' ->
|
||||||
withContactLock "forwardChatItem, to contact" toChatId $
|
withContactLock "forwardChatItem, to contact" toChatId $
|
||||||
sendContactContentMessages user toChatId False itemTTL cmrs'
|
sendContactContentMessages user toChatId False itemTTL cmrs'
|
||||||
Nothing -> throwChatError $ CEInternalError "no chat items to forward"
|
Nothing -> pure $ CRNewChatItems user []
|
||||||
CTGroup -> do
|
CTGroup -> do
|
||||||
cmrs <- prepareForward user
|
cmrs <- prepareForward user
|
||||||
case L.nonEmpty cmrs of
|
case L.nonEmpty cmrs of
|
||||||
Just cmrs' ->
|
Just cmrs' ->
|
||||||
withGroupLock "forwardChatItem, to group" toChatId $
|
withGroupLock "forwardChatItem, to group" toChatId $
|
||||||
sendGroupContentMessages user toChatId False itemTTL cmrs'
|
sendGroupContentMessages user toChatId False itemTTL cmrs'
|
||||||
Nothing -> throwChatError $ CEInternalError "no chat items to forward"
|
Nothing -> pure $ CRNewChatItems user []
|
||||||
CTLocal -> do
|
CTLocal -> do
|
||||||
cmrs <- prepareForward user
|
cmrs <- prepareForward user
|
||||||
case L.nonEmpty cmrs of
|
case L.nonEmpty cmrs of
|
||||||
Just cmrs' ->
|
Just cmrs' ->
|
||||||
createNoteFolderContentItems user toChatId cmrs'
|
createNoteFolderContentItems user toChatId cmrs'
|
||||||
Nothing -> throwChatError $ CEInternalError "no chat items to forward"
|
Nothing -> pure $ CRNewChatItems user []
|
||||||
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
|
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
|
||||||
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
|
||||||
where
|
where
|
||||||
prepareForward :: User -> CM [ComposeMessageReq]
|
prepareForward :: User -> CM [ComposeMessageReq]
|
||||||
prepareForward user = case fromCType of
|
prepareForward user = case fromCType of
|
||||||
CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do
|
CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do
|
||||||
ct <- withFastStore $ \db -> getContact db vr user fromChatId
|
(ct, items) <- getCommandDirectChatItems user fromChatId itemIds
|
||||||
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds))
|
catMaybes <$> mapM (\ci -> ciComposeMsgReq ct ci <$$> prepareMsgReq ci) items
|
||||||
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
|
||||||
mapM (ciComposeMsgReq ct) items
|
|
||||||
where
|
where
|
||||||
getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect))
|
ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq
|
||||||
getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId
|
ciComposeMsgReq ct (CChatItem md ci) (mc', file) =
|
||||||
ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq
|
|
||||||
ciComposeMsgReq ct (CChatItem _ ci) = do
|
|
||||||
(mc, mDir) <- forwardMC ci
|
|
||||||
file <- forwardCryptoFile ci
|
|
||||||
let itemId = chatItemId' ci
|
let itemId = chatItemId' ci
|
||||||
ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId))
|
ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) (toMsgDirection md) (Just fromChatId) (Just itemId))
|
||||||
pure (ComposedMessage file Nothing mc, ciff)
|
in (ComposedMessage file Nothing mc', ciff)
|
||||||
where
|
where
|
||||||
forwardName :: Contact -> ContactName
|
forwardName :: Contact -> ContactName
|
||||||
forwardName Contact {profile = LocalProfile {displayName, localAlias}}
|
forwardName Contact {profile = LocalProfile {displayName, localAlias}}
|
||||||
| localAlias /= "" = localAlias
|
| localAlias /= "" = localAlias
|
||||||
| otherwise = displayName
|
| otherwise = displayName
|
||||||
CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do
|
CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do
|
||||||
gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId
|
(gInfo, items) <- getCommandGroupChatItems user fromChatId itemIds
|
||||||
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds))
|
catMaybes <$> mapM (\ci -> ciComposeMsgReq gInfo ci <$$> prepareMsgReq ci) items
|
||||||
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
|
||||||
mapM (ciComposeMsgReq gInfo) items
|
|
||||||
where
|
where
|
||||||
getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup))
|
ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq
|
||||||
getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId
|
ciComposeMsgReq gInfo (CChatItem md ci) (mc', file) = do
|
||||||
ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq
|
|
||||||
ciComposeMsgReq gInfo (CChatItem _ ci) = do
|
|
||||||
(mc, mDir) <- forwardMC ci
|
|
||||||
file <- forwardCryptoFile ci
|
|
||||||
let itemId = chatItemId' ci
|
let itemId = chatItemId' ci
|
||||||
ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId))
|
ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) (toMsgDirection md) (Just fromChatId) (Just itemId))
|
||||||
pure (ComposedMessage file Nothing mc, ciff)
|
in (ComposedMessage file Nothing mc', ciff)
|
||||||
where
|
where
|
||||||
forwardName :: GroupInfo -> ContactName
|
forwardName :: GroupInfo -> ContactName
|
||||||
forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName
|
forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName
|
||||||
CTLocal -> do
|
CTLocal -> do
|
||||||
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds))
|
(_, items) <- getCommandLocalChatItems user fromChatId itemIds
|
||||||
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
catMaybes <$> mapM (\ci -> ciComposeMsgReq ci <$$> prepareMsgReq ci) items
|
||||||
mapM ciComposeMsgReq items
|
|
||||||
where
|
where
|
||||||
getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal))
|
ciComposeMsgReq :: CChatItem 'CTLocal -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq
|
||||||
getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId
|
ciComposeMsgReq (CChatItem _ ci) (mc', file) =
|
||||||
ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq
|
|
||||||
ciComposeMsgReq (CChatItem _ ci) = do
|
|
||||||
(mc, _) <- forwardMC ci
|
|
||||||
file <- forwardCryptoFile ci
|
|
||||||
let ciff = forwardCIFF ci Nothing
|
let ciff = forwardCIFF ci Nothing
|
||||||
pure (ComposedMessage file Nothing mc, ciff)
|
in (ComposedMessage file Nothing mc', ciff)
|
||||||
CTContactRequest -> throwChatError $ CECommandError "not supported"
|
CTContactRequest -> throwChatError $ CECommandError "not supported"
|
||||||
CTContactConnection -> throwChatError $ CECommandError "not supported"
|
CTContactConnection -> throwChatError $ CECommandError "not supported"
|
||||||
where
|
where
|
||||||
forwardMC :: ChatItem c d -> CM (MsgContent, MsgDirection)
|
prepareMsgReq :: CChatItem c -> CM (Maybe (MsgContent, Maybe CryptoFile))
|
||||||
forwardMC ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidForward
|
prepareMsgReq (CChatItem _ ci) = forwardMsgContent ci $>>= forwardContent ci
|
||||||
forwardMC ChatItem {content = CISndMsgContent fmc} = pure (fmc, MDSnd)
|
|
||||||
forwardMC ChatItem {content = CIRcvMsgContent fmc} = pure (fmc, MDRcv)
|
|
||||||
forwardMC _ = throwChatError CEInvalidForward
|
|
||||||
forwardCIFF :: ChatItem c d -> Maybe CIForwardedFrom -> Maybe CIForwardedFrom
|
forwardCIFF :: ChatItem c d -> Maybe CIForwardedFrom -> Maybe CIForwardedFrom
|
||||||
forwardCIFF ChatItem {meta = CIMeta {itemForwarded}} ciff = case itemForwarded of
|
forwardCIFF ChatItem {meta = CIMeta {itemForwarded}} ciff = case itemForwarded of
|
||||||
Nothing -> ciff
|
Nothing -> ciff
|
||||||
Just CIFFUnknown -> ciff
|
Just CIFFUnknown -> ciff
|
||||||
Just prevCIFF -> Just prevCIFF
|
Just prevCIFF -> Just prevCIFF
|
||||||
forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile)
|
forwardContent :: ChatItem c d -> MsgContent -> CM (Maybe (MsgContent, Maybe CryptoFile))
|
||||||
forwardCryptoFile ChatItem {file = Nothing} = pure Nothing
|
forwardContent ChatItem {file = Nothing} mc = pure $ Just (mc, Nothing)
|
||||||
forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of
|
forwardContent ChatItem {file = Just ciFile} mc = case ciFile of
|
||||||
CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} ->
|
CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} ->
|
||||||
chatReadVar filesFolder >>= \case
|
chatReadVar filesFolder >>= \case
|
||||||
Nothing ->
|
Nothing ->
|
||||||
ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing)
|
ifM (doesFileExist filePath) (pure $ Just (mc, Just fromCF)) (pure contentWithoutFile)
|
||||||
Just filesFolder -> do
|
Just filesFolder -> do
|
||||||
let fsFromPath = filesFolder </> filePath
|
let fsFromPath = filesFolder </> filePath
|
||||||
ifM
|
ifM
|
||||||
@@ -1089,10 +1097,17 @@ processChatCommand' vr = \case
|
|||||||
let toCF = CryptoFile fsNewPath cfArgs
|
let toCF = CryptoFile fsNewPath cfArgs
|
||||||
-- to keep forwarded file in case original is deleted
|
-- to keep forwarded file in case original is deleted
|
||||||
liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF
|
liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF
|
||||||
pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile)
|
pure $ Just (mc, Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile))
|
||||||
)
|
)
|
||||||
(pure Nothing)
|
(pure contentWithoutFile)
|
||||||
_ -> pure Nothing
|
_ -> pure contentWithoutFile
|
||||||
|
where
|
||||||
|
contentWithoutFile = case mc of
|
||||||
|
MCImage {} -> Just (mc, Nothing)
|
||||||
|
MCLink {} -> Just (mc, Nothing)
|
||||||
|
_ | contentText /= "" -> Just (MCText contentText, Nothing)
|
||||||
|
_ -> Nothing
|
||||||
|
contentText = msgContentText mc
|
||||||
copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO ()
|
copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO ()
|
||||||
copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do
|
copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do
|
||||||
fromSizeFull <- getFileSize fsFromPath
|
fromSizeFull <- getFileSize fsFromPath
|
||||||
@@ -3087,6 +3102,38 @@ processChatCommand' vr = \case
|
|||||||
| (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <-
|
| (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <-
|
||||||
zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_)
|
zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_)
|
||||||
]
|
]
|
||||||
|
getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect])
|
||||||
|
getCommandDirectChatItems user ctId itemIds = do
|
||||||
|
ct <- withFastStore $ \db -> getContact db vr user ctId
|
||||||
|
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds))
|
||||||
|
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
||||||
|
pure (ct, items)
|
||||||
|
where
|
||||||
|
getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect))
|
||||||
|
getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user ctId itemId
|
||||||
|
getCommandGroupChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (GroupInfo, [CChatItem 'CTGroup])
|
||||||
|
getCommandGroupChatItems user gId itemIds = do
|
||||||
|
gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
|
||||||
|
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds))
|
||||||
|
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
||||||
|
pure (gInfo, items)
|
||||||
|
where
|
||||||
|
getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup))
|
||||||
|
getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user gId itemId
|
||||||
|
getCommandLocalChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (NoteFolder, [CChatItem 'CTLocal])
|
||||||
|
getCommandLocalChatItems user nfId itemIds = do
|
||||||
|
nf <- withStore $ \db -> getNoteFolder db user nfId
|
||||||
|
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds))
|
||||||
|
unless (null errs) $ toView $ CRChatErrors (Just user) errs
|
||||||
|
pure (nf, items)
|
||||||
|
where
|
||||||
|
getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal))
|
||||||
|
getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user nfId itemId
|
||||||
|
forwardMsgContent :: ChatItem c d -> CM (Maybe MsgContent)
|
||||||
|
forwardMsgContent ChatItem {meta = CIMeta {itemDeleted = Just _}} = pure Nothing -- this can be deleted after selection
|
||||||
|
forwardMsgContent ChatItem {content = CISndMsgContent fmc} = pure $ Just fmc
|
||||||
|
forwardMsgContent ChatItem {content = CIRcvMsgContent fmc} = pure $ Just fmc
|
||||||
|
forwardMsgContent _ = throwChatError CEInvalidForward
|
||||||
createNoteFolderContentItems :: User -> NoteFolderId -> NonEmpty ComposeMessageReq -> CM ChatResponse
|
createNoteFolderContentItems :: User -> NoteFolderId -> NonEmpty ComposeMessageReq -> CM ChatResponse
|
||||||
createNoteFolderContentItems user folderId cmrs = do
|
createNoteFolderContentItems user folderId cmrs = do
|
||||||
assertNoQuotes
|
assertNoQuotes
|
||||||
@@ -7888,6 +7935,7 @@ chatCommandP =
|
|||||||
"/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode),
|
"/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode),
|
||||||
"/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP),
|
"/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP),
|
||||||
"/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP),
|
"/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP),
|
||||||
|
"/_forward plan " *> (APIPlanForwardChatItems <$> chatRefP <*> _strP),
|
||||||
"/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP),
|
"/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP),
|
||||||
"/_read user " *> (APIUserRead <$> A.decimal),
|
"/_read user " *> (APIUserRead <$> A.decimal),
|
||||||
"/read user" $> UserRead,
|
"/read user" $> UserRead,
|
||||||
|
|||||||
@@ -298,6 +298,7 @@ data ChatCommand
|
|||||||
| APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode
|
| APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode
|
||||||
| APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId)
|
| APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId)
|
||||||
| APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction}
|
| APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction}
|
||||||
|
| APIPlanForwardChatItems {fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId}
|
||||||
| APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int}
|
| APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int}
|
||||||
| APIUserRead UserId
|
| APIUserRead UserId
|
||||||
| UserRead
|
| UserRead
|
||||||
@@ -649,6 +650,7 @@ data ChatResponse
|
|||||||
| CRContactRequestAlreadyAccepted {user :: User, contact :: Contact}
|
| CRContactRequestAlreadyAccepted {user :: User, contact :: Contact}
|
||||||
| CRLeftMemberUser {user :: User, groupInfo :: GroupInfo}
|
| CRLeftMemberUser {user :: User, groupInfo :: GroupInfo}
|
||||||
| CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo}
|
| CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo}
|
||||||
|
| CRForwardPlan {user :: User, itemsCount :: Int, chatItemIds :: [ChatItemId], forwardConfirmation :: Maybe ForwardConfirmation}
|
||||||
| CRRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr}
|
| CRRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr}
|
||||||
| CRRcvFileAccepted {user :: User, chatItem :: AChatItem}
|
| CRRcvFileAccepted {user :: User, chatItem :: AChatItem}
|
||||||
| CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer}
|
| CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer}
|
||||||
@@ -905,6 +907,13 @@ connectionPlanProceed = \case
|
|||||||
GLPConnectingConfirmReconnect -> True
|
GLPConnectingConfirmReconnect -> True
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
|
data ForwardConfirmation
|
||||||
|
= FCFilesNotAccepted {fileIds :: [FileTransferId]}
|
||||||
|
| FCFilesInProgress {filesCount :: Int}
|
||||||
|
| FCFilesMissing {filesCount :: Int}
|
||||||
|
| FCFilesFailed {filesCount :: Int}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
newtype UserPwd = UserPwd {unUserPwd :: Text}
|
newtype UserPwd = UserPwd {unUserPwd :: Text}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
@@ -1463,6 +1472,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan)
|
|||||||
|
|
||||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan)
|
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan)
|
||||||
|
|
||||||
|
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation)
|
||||||
|
|
||||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType)
|
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType)
|
||||||
|
|
||||||
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RHE") ''RemoteHostError)
|
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RHE") ''RemoteHostError)
|
||||||
|
|||||||
@@ -595,6 +595,27 @@ ciFileLoaded = \case
|
|||||||
CIFSRcvWarning {} -> False
|
CIFSRcvWarning {} -> False
|
||||||
CIFSInvalid {} -> False
|
CIFSInvalid {} -> False
|
||||||
|
|
||||||
|
data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing
|
||||||
|
deriving (Eq, Ord)
|
||||||
|
|
||||||
|
ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError
|
||||||
|
ciFileForwardError fId = \case
|
||||||
|
CIFSSndStored -> Nothing
|
||||||
|
CIFSSndTransfer {} -> Nothing
|
||||||
|
CIFSSndComplete -> Nothing
|
||||||
|
CIFSSndCancelled -> Nothing
|
||||||
|
CIFSSndError {} -> Nothing
|
||||||
|
CIFSSndWarning {} -> Nothing
|
||||||
|
CIFSRcvInvitation -> Just $ FFENotAccepted fId
|
||||||
|
CIFSRcvAccepted -> Just FFEInProgress
|
||||||
|
CIFSRcvTransfer {} -> Just FFEInProgress
|
||||||
|
CIFSRcvAborted -> Just $ FFENotAccepted fId
|
||||||
|
CIFSRcvCancelled -> Just FFEFailed
|
||||||
|
CIFSRcvComplete -> Nothing
|
||||||
|
CIFSRcvError {} -> Just FFEFailed
|
||||||
|
CIFSRcvWarning {} -> Just FFEFailed
|
||||||
|
CIFSInvalid {} -> Just FFEFailed
|
||||||
|
|
||||||
data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d)
|
data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d)
|
||||||
|
|
||||||
deriving instance Show ACIFileStatus
|
deriving instance Show ACIFileStatus
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
|||||||
CRUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um]
|
CRUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um]
|
||||||
CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m]
|
CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m]
|
||||||
CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"]
|
CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"]
|
||||||
|
CRForwardPlan u count itemIds fc -> ttyUser u $ viewForwardPlan count itemIds fc
|
||||||
CRRcvFileDescrReady _ _ _ _ -> []
|
CRRcvFileDescrReady _ _ _ _ -> []
|
||||||
CRRcvFileProgressXFTP {} -> []
|
CRRcvFileProgressXFTP {} -> []
|
||||||
CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci
|
CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci
|
||||||
@@ -930,6 +931,20 @@ viewUserContactLinkDeleted =
|
|||||||
"To create a new chat address use " <> highlight' "/ad"
|
"To create a new chat address use " <> highlight' "/ad"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
viewForwardPlan :: Int -> [ChatItemId] -> Maybe ForwardConfirmation -> [StyledString]
|
||||||
|
viewForwardPlan count itemIds = maybe [forwardCount] $ \fc -> [confirmation fc, forwardCount]
|
||||||
|
where
|
||||||
|
confirmation = \case
|
||||||
|
FCFilesNotAccepted fileIds -> plain $ "Files can be received: " <> intercalate ", " (map show fileIds)
|
||||||
|
FCFilesInProgress cnt -> plain $ "Still receiving " <> show cnt <> " file(s)"
|
||||||
|
FCFilesMissing cnt -> plain $ show cnt <> " file(s) are missing"
|
||||||
|
FCFilesFailed cnt -> plain $ "Receiving " <> show cnt <> " file(s) failed"
|
||||||
|
forwardCount
|
||||||
|
| count == len = "all messages can be forwarded"
|
||||||
|
| len == 0 = "nothing to forward"
|
||||||
|
| otherwise = plain $ show len <> " message(s) out of " <> show count <> " can be forwarded"
|
||||||
|
len = length itemIds
|
||||||
|
|
||||||
connReqContact_ :: StyledString -> ConnReqContact -> [StyledString]
|
connReqContact_ :: StyledString -> ConnReqContact -> [StyledString]
|
||||||
connReqContact_ intro cReq =
|
connReqContact_ intro cReq =
|
||||||
[ intro,
|
[ intro,
|
||||||
@@ -2034,7 +2049,7 @@ viewChatError isCmd logLevel testView = \case
|
|||||||
CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"]
|
CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"]
|
||||||
CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."]
|
CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."]
|
||||||
CEInvalidQuote -> ["cannot reply to this message"]
|
CEInvalidQuote -> ["cannot reply to this message"]
|
||||||
CEInvalidForward -> ["cannot forward this message"]
|
CEInvalidForward -> ["cannot forward message(s)"]
|
||||||
CEInvalidChatItemUpdate -> ["cannot update this item"]
|
CEInvalidChatItemUpdate -> ["cannot update this item"]
|
||||||
CEInvalidChatItemDelete -> ["cannot delete this item"]
|
CEInvalidChatItemDelete -> ["cannot delete this item"]
|
||||||
CEHasCurrentCall -> ["call already in progress"]
|
CEHasCurrentCall -> ["call already in progress"]
|
||||||
|
|||||||
+73
-18
@@ -7,7 +7,11 @@ import ChatClient
|
|||||||
import ChatTests.Utils
|
import ChatTests.Utils
|
||||||
import Control.Concurrent (threadDelay)
|
import Control.Concurrent (threadDelay)
|
||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import System.Directory (copyFile, doesFileExist)
|
import Data.List (intercalate)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import System.Directory (copyFile, doesFileExist, removeFile)
|
||||||
|
import Simplex.Chat (fixedImagePreview)
|
||||||
|
import Simplex.Chat.Types (ImageData (..))
|
||||||
import Test.Hspec hiding (it)
|
import Test.Hspec hiding (it)
|
||||||
|
|
||||||
chatForwardTests :: SpecWith FilePath
|
chatForwardTests :: SpecWith FilePath
|
||||||
@@ -613,6 +617,8 @@ testForwardContactToContactMulti =
|
|||||||
alice <# "bob> hey"
|
alice <# "bob> hey"
|
||||||
msgId2 <- lastItemId alice
|
msgId2 <- lastItemId alice
|
||||||
|
|
||||||
|
alice ##> ("/_forward plan @2 " <> msgId1 <> "," <> msgId2)
|
||||||
|
alice <## "all messages can be forwarded"
|
||||||
alice ##> ("/_forward @3 @2 " <> msgId1 <> "," <> msgId2)
|
alice ##> ("/_forward @3 @2 " <> msgId1 <> "," <> msgId2)
|
||||||
alice <# "@cath <- you @bob"
|
alice <# "@cath <- you @bob"
|
||||||
alice <## " hi"
|
alice <## " hi"
|
||||||
@@ -642,6 +648,8 @@ testForwardGroupToGroupMulti =
|
|||||||
alice <# "#team bob> hey"
|
alice <# "#team bob> hey"
|
||||||
msgId2 <- lastItemId alice
|
msgId2 <- lastItemId alice
|
||||||
|
|
||||||
|
alice ##> ("/_forward plan #1 " <> msgId1 <> "," <> msgId2)
|
||||||
|
alice <## "all messages can be forwarded"
|
||||||
alice ##> ("/_forward #2 #1 " <> msgId1 <> "," <> msgId2)
|
alice ##> ("/_forward #2 #1 " <> msgId1 <> "," <> msgId2)
|
||||||
alice <# "#club <- you #team"
|
alice <# "#club <- you #team"
|
||||||
alice <## " hi"
|
alice <## " hi"
|
||||||
@@ -672,6 +680,7 @@ testMultiForwardFiles =
|
|||||||
setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp"
|
setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp"
|
||||||
copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg"
|
copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg"
|
||||||
copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf"
|
copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf"
|
||||||
|
copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf"
|
||||||
setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp"
|
setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp"
|
||||||
setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp"
|
setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp"
|
||||||
connectUsers alice bob
|
connectUsers alice bob
|
||||||
@@ -686,32 +695,46 @@ testMultiForwardFiles =
|
|||||||
|
|
||||||
-- send original files
|
-- send original files
|
||||||
let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}"
|
let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}"
|
||||||
cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}"
|
ImageData img = fixedImagePreview
|
||||||
cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}"
|
cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"image\", \"image\":\"" <> T.unpack img <> "\", \"text\": \"\"}}"
|
||||||
alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]")
|
cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}"
|
||||||
|
cm4 = "{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"message with large file\"}}"
|
||||||
|
alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "]")
|
||||||
|
|
||||||
alice <# "@bob message without file"
|
alice <# "@bob message without file"
|
||||||
|
|
||||||
alice <# "@bob sending file 1"
|
|
||||||
alice <# "/f @bob test.jpg"
|
alice <# "/f @bob test.jpg"
|
||||||
alice <## "use /fc 1 to cancel sending"
|
alice <## "use /fc 1 to cancel sending"
|
||||||
|
|
||||||
alice <# "@bob sending file 2"
|
|
||||||
alice <# "/f @bob test.pdf"
|
alice <# "/f @bob test.pdf"
|
||||||
alice <## "use /fc 2 to cancel sending"
|
alice <## "use /fc 2 to cancel sending"
|
||||||
|
|
||||||
|
alice <# "@bob message with large file"
|
||||||
|
alice <# "/f @bob test_1MB.pdf"
|
||||||
|
alice <## "use /fc 3 to cancel sending"
|
||||||
|
|
||||||
bob <# "alice> message without file"
|
bob <# "alice> message without file"
|
||||||
|
|
||||||
bob <# "alice> sending file 1"
|
|
||||||
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
|
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
|
||||||
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||||
|
|
||||||
bob <# "alice> sending file 2"
|
|
||||||
bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)"
|
bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)"
|
||||||
bob <## "use /fr 2 [<dir>/ | <path>] to receive it"
|
bob <## "use /fr 2 [<dir>/ | <path>] to receive it"
|
||||||
|
|
||||||
|
bob <# "alice> message with large file"
|
||||||
|
bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)"
|
||||||
|
bob <## "use /fr 3 [<dir>/ | <path>] to receive it"
|
||||||
|
|
||||||
alice <## "completed uploading file 1 (test.jpg) for bob"
|
alice <## "completed uploading file 1 (test.jpg) for bob"
|
||||||
alice <## "completed uploading file 2 (test.pdf) for bob"
|
alice <## "completed uploading file 2 (test.pdf) for bob"
|
||||||
|
alice <## "completed uploading file 3 (test_1MB.pdf) for bob"
|
||||||
|
|
||||||
|
-- IDs to forward
|
||||||
|
let msgId1 = (read msgIdZero :: Int) + 1
|
||||||
|
msgIds = intercalate "," $ map show [msgId1, msgId1 + 1, msgId1 + 2, msgId1 + 3, msgId1 + 4]
|
||||||
|
bob ##> ("/_forward plan @2 " <> msgIds)
|
||||||
|
bob <## "Files can be received: 1, 2, 3"
|
||||||
|
bob <## "4 message(s) out of 5 can be forwarded"
|
||||||
|
|
||||||
bob ##> "/fr 1"
|
bob ##> "/fr 1"
|
||||||
bob
|
bob
|
||||||
@@ -720,6 +743,10 @@ testMultiForwardFiles =
|
|||||||
]
|
]
|
||||||
bob <## "completed receiving file 1 (test.jpg) from alice"
|
bob <## "completed receiving file 1 (test.jpg) from alice"
|
||||||
|
|
||||||
|
bob ##> ("/_forward plan @2 " <> msgIds)
|
||||||
|
bob <## "Files can be received: 2, 3"
|
||||||
|
bob <## "4 message(s) out of 5 can be forwarded"
|
||||||
|
|
||||||
bob ##> "/fr 2"
|
bob ##> "/fr 2"
|
||||||
bob
|
bob
|
||||||
<### [ "saving file 2 from alice to test.pdf",
|
<### [ "saving file 2 from alice to test.pdf",
|
||||||
@@ -736,8 +763,10 @@ testMultiForwardFiles =
|
|||||||
dest2 `shouldBe` src2
|
dest2 `shouldBe` src2
|
||||||
|
|
||||||
-- forward file
|
-- forward file
|
||||||
let msgId1 = (read msgIdZero :: Int) + 1
|
bob ##> ("/_forward plan @2 " <> msgIds)
|
||||||
bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3))
|
bob <## "Files can be received: 3"
|
||||||
|
bob <## "all messages can be forwarded"
|
||||||
|
bob ##> ("/_forward @3 @2 " <> msgIds)
|
||||||
|
|
||||||
-- messages printed for bob
|
-- messages printed for bob
|
||||||
bob <# "@cath <- you @alice"
|
bob <# "@cath <- you @alice"
|
||||||
@@ -747,14 +776,17 @@ testMultiForwardFiles =
|
|||||||
bob <## " message without file"
|
bob <## " message without file"
|
||||||
|
|
||||||
bob <# "@cath <- @alice"
|
bob <# "@cath <- @alice"
|
||||||
bob <## " sending file 1"
|
bob <## " test_1.jpg"
|
||||||
bob <# "/f @cath test_1.jpg"
|
bob <# "/f @cath test_1.jpg"
|
||||||
bob <## "use /fc 3 to cancel sending"
|
bob <## "use /fc 4 to cancel sending"
|
||||||
|
|
||||||
bob <# "@cath <- @alice"
|
bob <# "@cath <- @alice"
|
||||||
bob <## " sending file 2"
|
bob <## " test_1.pdf"
|
||||||
bob <# "/f @cath test_1.pdf"
|
bob <# "/f @cath test_1.pdf"
|
||||||
bob <## "use /fc 4 to cancel sending"
|
bob <## "use /fc 5 to cancel sending"
|
||||||
|
|
||||||
|
bob <# "@cath <- @alice"
|
||||||
|
bob <## " message with large file"
|
||||||
|
|
||||||
-- messages printed for cath
|
-- messages printed for cath
|
||||||
cath <# "bob> -> forwarded"
|
cath <# "bob> -> forwarded"
|
||||||
@@ -764,18 +796,21 @@ testMultiForwardFiles =
|
|||||||
cath <## " message without file"
|
cath <## " message without file"
|
||||||
|
|
||||||
cath <# "bob> -> forwarded"
|
cath <# "bob> -> forwarded"
|
||||||
cath <## " sending file 1"
|
cath <## " test_1.jpg"
|
||||||
cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)"
|
cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)"
|
||||||
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||||
|
|
||||||
cath <# "bob> -> forwarded"
|
cath <# "bob> -> forwarded"
|
||||||
cath <## " sending file 2"
|
cath <## " test_1.pdf"
|
||||||
cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)"
|
cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)"
|
||||||
cath <## "use /fr 2 [<dir>/ | <path>] to receive it"
|
cath <## "use /fr 2 [<dir>/ | <path>] to receive it"
|
||||||
|
|
||||||
|
cath <# "bob> -> forwarded"
|
||||||
|
cath <## " message with large file"
|
||||||
|
|
||||||
-- file transfer
|
-- file transfer
|
||||||
bob <## "completed uploading file 3 (test_1.jpg) for cath"
|
bob <## "completed uploading file 4 (test_1.jpg) for cath"
|
||||||
bob <## "completed uploading file 4 (test_1.pdf) for cath"
|
bob <## "completed uploading file 5 (test_1.pdf) for cath"
|
||||||
|
|
||||||
cath ##> "/fr 1"
|
cath ##> "/fr 1"
|
||||||
cath
|
cath
|
||||||
@@ -801,6 +836,26 @@ testMultiForwardFiles =
|
|||||||
dest2C <- B.readFile "./tests/tmp/cath_app_files/test_1.pdf"
|
dest2C <- B.readFile "./tests/tmp/cath_app_files/test_1.pdf"
|
||||||
dest2C `shouldBe` src2B
|
dest2C `shouldBe` src2B
|
||||||
|
|
||||||
|
bob ##> "/fr 3"
|
||||||
|
bob
|
||||||
|
<### [ "saving file 3 from alice to test_1MB.pdf",
|
||||||
|
"started receiving file 3 (test_1MB.pdf) from alice"
|
||||||
|
]
|
||||||
|
bob <## "completed receiving file 3 (test_1MB.pdf) from alice"
|
||||||
|
|
||||||
|
bob ##> ("/_forward plan @2 " <> msgIds)
|
||||||
|
bob <## "all messages can be forwarded"
|
||||||
|
|
||||||
|
removeFile "./tests/tmp/bob_app_files/test_1MB.pdf"
|
||||||
|
bob ##> ("/_forward plan @2 " <> msgIds)
|
||||||
|
bob <## "1 file(s) are missing"
|
||||||
|
bob <## "all messages can be forwarded"
|
||||||
|
|
||||||
|
removeFile "./tests/tmp/bob_app_files/test.pdf"
|
||||||
|
bob ##> ("/_forward plan @2 " <> msgIds)
|
||||||
|
bob <## "2 file(s) are missing"
|
||||||
|
bob <## "4 message(s) out of 5 can be forwarded"
|
||||||
|
|
||||||
-- deleting original file doesn't delete forwarded file
|
-- deleting original file doesn't delete forwarded file
|
||||||
checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do
|
checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do
|
||||||
bob ##> "/clear alice"
|
bob ##> "/clear alice"
|
||||||
|
|||||||
Reference in New Issue
Block a user