diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 05bf62f7fa..c882b351e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -605,6 +605,9 @@ jobs: - name: Checkout current repository uses: actions/checkout@v6 + - name: Install packages for archiving + run: sudo apt install -y msitools gcc-mingw-w64 + - name: Build archives run: | INIT_DIR='${{ runner.temp }}/artifacts' @@ -612,6 +615,8 @@ jobs: TAG='${{ github.ref_name }}' URL='https://github.com/${{ github.repository }}/releases/download' PREFIX='${{ github.event.repository.name }}-libs' + # Windows-specific + FILE_URL='https://raw.githubusercontent.com/${{ github.repository }}/refs/tags/${{ github.ref_name }}' # Setup directories mkdir "$INIT_DIR" "$RELEASE_DIR" && cd "$INIT_DIR" @@ -620,6 +625,7 @@ jobs: curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-ubuntu-22_04-x86_64.deb" -o linux.deb curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-macos-aarch64.dmg" -o macos-aarch64.dmg curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-macos-x86_64.dmg" -o macos-x86_64.dmg + curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-windows-x86_64.msi" -o windows-x86_64.msi # Linux # ----- @@ -646,6 +652,18 @@ jobs: zip -r "${PREFIX}-macos-x86_64.zip" libs mv "${PREFIX}-macos-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR" + # Windows: x86_64 + # --------------- + msiextract windows-x86_64.msi -C windows-out && cd windows-out/SimpleX/app/resources + + # We need to generate library that exports symbols from Windows dll + curl --proto '=https' --tlsv1.2 -sSf -LO "${FILE_URL}/libsimplex.dll.def" + x86_64-w64-mingw32-dlltool -d libsimplex.dll.def -l libsimplex.lib -D libsimplex.dll + + mkdir libs && cp *.dll *.lib libs/ + zip -r "${PREFIX}-windows-x86_64.zip" libs + mv "${PREFIX}-windows-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR" + - name: Create release in libs repo and upload artifacts uses: softprops/action-gh-release@v2 with: diff --git a/README.md b/README.md index 3364c28284..fc31c522ca 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ ## Connect to the team -You can connect to the team via the app using "chat with the developers button" available when you have no conversations in the profile, "Send questions and ideas" in the app settings or via our [SimpleX address](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). Please connect to: +You can connect to the team via the app using "chat with the developers button" available when you have no conversations in the profile, "Send questions and ideas" in the app settings or via our [SimpleX address](https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw). Please connect to: - to ask any questions - to suggest any improvements @@ -54,7 +54,7 @@ If you are interested in helping us to integrate open-source language models, an ## Join user groups -You can find the groups created by users in [SimpleX Directory](https://simplex.chat/directory/). It is also available as [SimpleX bot](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) that allows to add your own groups and communities to the directory. We are not responsible for the content shared in these groups. +You can find the groups created by users in [SimpleX Directory](https://simplex.chat/directory/). It is also available as [SimpleX bot](https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok) that allows to add your own groups and communities to the directory. We are not responsible for the content shared in these groups. **Please note**: The groups below are created for the users to be able to ask questions, make suggestions and ask questions about SimpleX Chat only. diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs index d9f091a13a..ff853f403d 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs @@ -94,6 +94,5 @@ mkChatOpts BroadcastBotOpts {coreOptions, botDisplayName} = autoAcceptFileSize = 0, muteNotifications = True, markRead = False, - createBot = Just CreateBotOpts {botDisplayName, allowFiles = False}, - maintenance = False + createBot = Just CreateBotOpts {botDisplayName, allowFiles = False} } diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs index fb82c27b78..daebd864d6 100644 --- a/apps/simplex-directory-service/src/Directory/Options.hs +++ b/apps/simplex-directory-service/src/Directory/Options.hs @@ -27,6 +27,7 @@ data DirectoryOpts = DirectoryOpts adminUsers :: [KnownContact], superUsers :: [KnownContact], ownersGroup :: Maybe KnownGroup, + noAddress :: Bool, -- skip creating address blockedWordsFile :: Maybe FilePath, blockedFragmentsFile :: Maybe FilePath, blockedExtensionRules :: Maybe FilePath, @@ -70,6 +71,11 @@ directoryOpts appDir defaultDbName = do <> metavar "OWNERS_GROUP" <> help "The group of group owners in the format GROUP_ID:DISPLAY_NAME - owners of listed groups will be invited automatically" ) + noAddress <- + switch + ( long "no-address" + <> help "skip checking and creating service address" + ) blockedWordsFile <- optional $ strOption @@ -153,6 +159,7 @@ directoryOpts appDir defaultDbName = do adminUsers, superUsers, ownersGroup, + noAddress, blockedWordsFile, blockedFragmentsFile, blockedExtensionRules, @@ -194,8 +201,7 @@ mkChatOpts DirectoryOpts {coreOptions, serviceName} = autoAcceptFileSize = 0, muteNotifications = True, markRead = False, - createBot = Just CreateBotOpts {botDisplayName = serviceName, allowFiles = False}, - maintenance = False + createBot = Just CreateBotOpts {botDisplayName = serviceName, allowFiles = False} } parseMigrateLog :: ReadM MigrateLog diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 4be7b6177e..41ea081890 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -184,10 +184,11 @@ directoryPreStartHook :: DirectoryOpts -> ChatController -> IO () directoryPreStartHook opts ChatController {config, chatStore} = runDirectoryMigrations opts config chatStore directoryPostStartHook :: DirectoryOpts -> ServiceState -> ChatController -> IO () -directoryPostStartHook opts env cc = +directoryPostStartHook opts@DirectoryOpts {noAddress, testing} env cc = readTVarIO (currentUser cc) >>= \case Nothing -> putStrLn "No current user" >> exitFailure Just User {userId, profile = p@LocalProfile {preferences}} -> do + unless noAddress $ initializeBotAddress' (not testing) cc listingsUpdated env cc let cmds = fromMaybe [] $ preferences >>= commands_ unless (cmds == directoryCommands) $ do @@ -216,7 +217,7 @@ directoryCommands = idParam = Just "" directoryService :: DirectoryLog -> DirectoryOpts -> ChatConfig -> IO () -directoryService st opts@DirectoryOpts {testing} cfg = do +directoryService st opts cfg = do env <- newServiceState opts let chatHooks = defaultChatHooks @@ -224,8 +225,7 @@ directoryService st opts@DirectoryOpts {testing} cfg = do postStartHook = Just $ directoryPostStartHook opts env, acceptMember = Just $ acceptMemberHook opts env } - simplexChatCore cfg {chatHooks} (mkChatOpts opts) $ \user cc -> do - initializeBotAddress' (not testing) cc + simplexChatCore cfg {chatHooks} (mkChatOpts opts) $ \user cc -> raceAny_ $ [ forever $ void getLine, forever $ do diff --git a/cabal.project b/cabal.project index 2f478dbc0f..390890d258 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 58212c421aa6abb6ad894b8231d8a380849b704b + tag: ca26c69937083deee43b8b2200ec9ef4c004ceac source-repository-package type: git diff --git a/packages/simplex-chat-nodejs/README.md b/packages/simplex-chat-nodejs/README.md index 982b8d0553..e75dab3f96 100644 --- a/packages/simplex-chat-nodejs/README.md +++ b/packages/simplex-chat-nodejs/README.md @@ -14,7 +14,7 @@ Please share your use cases and implementations. ## Quick start: a simple bot ``` -npm i simplex-chat@6.5.0-beta.4.2 +npm i simplex-chat@6.5.0-beta.4.4 ``` Simple bot that replies with squares of numbers you send to it: @@ -28,7 +28,7 @@ Simple bot that replies with squares of numbers you send to it: profile: {displayName: "Squaring bot example", fullName: ""}, dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, options: { - addressSettings: {welcomeMessage: "If you send me a number, I will calculate its square."}, + addressSettings: {welcomeMessage: "Send a number, I will square it.", }, onMessage: async (ci, content) => { const n = +content.text @@ -44,9 +44,11 @@ Simple bot that replies with squares of numbers you send to it: If you installed this package as dependency, you can run this example with: ```sh -node ./node_modules/simplex-chat/examples/squaring-bot-readme.js` +node ./node_modules/simplex-chat/examples/squaring-bot-readme.js ``` +If you run it on Mac, the first time it will take 20-30 seconds for MacOS to verify the library. + If you cloned this repository, you can: ``` diff --git a/packages/simplex-chat-nodejs/binding.gyp b/packages/simplex-chat-nodejs/binding.gyp index addb293f5b..09c63cecba 100644 --- a/packages/simplex-chat-nodejs/binding.gyp +++ b/packages/simplex-chat-nodejs/binding.gyp @@ -9,15 +9,15 @@ "dependencies": [ "(argv); hs_init_with_rtsopts(&argc, &pargv); } diff --git a/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js index f880808bcf..16d0678b64 100644 --- a/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js +++ b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js @@ -4,7 +4,7 @@ profile: {displayName: "Squaring bot example", fullName: ""}, dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, options: { - addressSettings: {welcomeMessage: "If you send me a number, I will calculate its square."}, + addressSettings: {welcomeMessage: "Send a number, I will square it."}, }, onMessage: async (ci, content) => { const n = +content.text diff --git a/packages/simplex-chat-nodejs/package.json b/packages/simplex-chat-nodejs/package.json index 1ef1429e65..498d502edd 100644 --- a/packages/simplex-chat-nodejs/package.json +++ b/packages/simplex-chat-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "simplex-chat", - "version": "6.5.0-beta.4.2", + "version": "6.5.0-beta.4.4", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index a9deb28d9a..7868a125b6 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh index 782610b302..6013fc55c0 100755 --- a/scripts/desktop/build-lib-mac.sh +++ b/scripts/desktop/build-lib-mac.sh @@ -3,7 +3,7 @@ set -e OS=mac -ARCH="${1:-`uname -a | rev | cut -d' ' -f1 | rev`}" +ARCH="${1:-$(uname -m)}" COMPOSE_ARCH=$ARCH GHC_VERSION=9.6.3 DATABASE_BACKEND="${2:-sqlite}" diff --git a/scripts/desktop/make-appimage-linux.sh b/scripts/desktop/make-appimage-linux.sh index 5978fe0cba..c242b63d54 100755 --- a/scripts/desktop/make-appimage-linux.sh +++ b/scripts/desktop/make-appimage-linux.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/scripts/desktop/prepare-vlc-linux.sh b/scripts/desktop/prepare-vlc-linux.sh index 6106035d83..ef1ee1b308 100755 --- a/scripts/desktop/prepare-vlc-linux.sh +++ b/scripts/desktop/prepare-vlc-linux.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/scripts/desktop/prepare-vlc-mac.sh b/scripts/desktop/prepare-vlc-mac.sh index 4db2983f67..180acf4426 100755 --- a/scripts/desktop/prepare-vlc-mac.sh +++ b/scripts/desktop/prepare-vlc-mac.sh @@ -2,7 +2,7 @@ set -e -ARCH="${1:-`uname -a | rev | cut -d' ' -f1 | rev`}" +ARCH="${1:-$(uname -m)}" if [ "$ARCH" == "arm64" ]; then ARCH=aarch64 vlc_arch=arm64 diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index f080cb1118..31166762bc 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."58212c421aa6abb6ad894b8231d8a380849b704b" = "1awgvhqfi7gv3xl10h21a6w2hhqc48pq6yq4f83awg1zxkh3hiqn"; + "https://github.com/simplex-chat/simplexmq.git"."ca26c69937083deee43b8b2200ec9ef4c004ceac" = "1p7jhxcbn95kddfwa5rjpzfx78fzic03wmy9dmh1mj3j14vyfn02"; "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/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/scripts/simplex-chat-reproduce-builds-android.sh b/scripts/simplex-chat-reproduce-builds-android.sh index 0cef17c091..20c2fdf116 100755 --- a/scripts/simplex-chat-reproduce-builds-android.sh +++ b/scripts/simplex-chat-reproduce-builds-android.sh @@ -18,6 +18,7 @@ CMDS="curl git docker" INIT_DIR="$PWD" TEMPDIR="$(mktemp -d)" +PID_MAX_ORIGINAL="$(sysctl -n kernel.pid_max)" ARCHES="${ARCHES:-aarch64 armv7a}" @@ -32,6 +33,15 @@ cleanup() { rm -rf -- "${TEMPDIR}" docker rm --force "${CONTAINER_NAME}" 2>/dev/null || : docker image rm "${IMAGE_NAME}" 2>/dev/null || : + + if [ "$(sysctl -n kernel.pid_max)" != "$PID_MAX_ORIGINAL" ]; then + printf 'Adjusting kernel.pid_max back to original value...\n' + if $SUDO sysctl kernel.pid_max="$PID_MAX_ORIGINAL"; then + printf 'Successfully adjusted kernel.pid_max\n' + else + printf 'Failed to adjust kernel.pid_max. Please set the value manually with: %s sysctl kernel.pid_max=%s\n' "$SUDO" "$PID_MAX_ORIGINAL" + fi + fi } trap 'cleanup' EXIT INT @@ -52,6 +62,19 @@ check() { exit 1 fi + if [ "$PID_MAX_ORIGINAL" -gt 65535 ]; then + SUDO=$(command -v sudo || command -v doas) || { echo "No sudo or doas"; exit 1; } + + printf 'Adjusting kernel.pid_max value to 65535...\n' + + if $SUDO sysctl kernel.pid_max=65535; then + printf 'Successfully adjusted kernel.pid_max\n' + else + printf 'Failed to adjust kernel.pid_max, aborting.\n' + exit 1 + fi + fi + set -u } @@ -182,6 +205,9 @@ main() { 3) build core library with nix (12-24 hours). 4) build APK and compare with downloaded one +The script will ask for sudo password to adjust kernel.pid_max (needed for armv7a build) +and set it back to otiginal value when the build is done. + Continue?' read _ diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index 5837e64f4d..52622a36de 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -38,7 +38,7 @@ import Text.Read (readMaybe) import UnliftIO.Async simplexChatCore :: ChatConfig -> ChatOpts -> (User -> ChatController -> IO ()) -> IO () -simplexChatCore cfg@ChatConfig {confirmMigrations, testView, chatHooks} opts@ChatOpts {coreOptions = CoreChatOpts {dbOptions, logAgent, yesToUpMigrations, migrationBackupPath}, createBot, maintenance} chat = +simplexChatCore cfg@ChatConfig {confirmMigrations, testView, chatHooks} opts@ChatOpts {coreOptions = CoreChatOpts {dbOptions, logAgent, yesToUpMigrations, migrationBackupPath, maintenance}, createBot} chat = case logAgent of Just level -> do setLogLevel level @@ -54,13 +54,16 @@ simplexChatCore cfg@ChatConfig {confirmMigrations, testView, chatHooks} opts@Cha u_ <- getSelectActiveUser chatStore let backgroundMode = maintenance cc <- newChatController db u_ cfg opts backgroundMode - u <- maybe (createActiveUser cc createBot) pure u_ + forM_ (preStartHook chatHooks) ($ cc) + u <- maybe (noMaintenance >> createActiveUser cc createBot) pure u_ unless testView $ putStrLn $ "Current user: " <> userStr u - unless maintenance $ forM_ (preStartHook chatHooks) ($ cc) runSimplexChat opts u cc chat + noMaintenance = when maintenance $ do + putStrLn "exiting: no active user in maintenance mode" + exitFailure runSimplexChat :: ChatOpts -> User -> ChatController -> (User -> ChatController -> IO ()) -> IO () -runSimplexChat ChatOpts {maintenance} u cc@ChatController {config = ChatConfig {chatHooks}} chat +runSimplexChat ChatOpts {coreOptions = CoreChatOpts {maintenance}} u cc@ChatController {config = ChatConfig {chatHooks}} chat | maintenance = wait =<< async (chat u cc) | otherwise = do a1 <- runReaderT (startChatController True True) cc diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 3c8f170b0d..25e632b933 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -255,7 +255,8 @@ mobileChatOpts dbOptions = deviceName = Nothing, highlyAvailable = False, yesToUpMigrations = False, - migrationBackupPath = Just "" + migrationBackupPath = Just "", + maintenance = True }, chatCmd = "", chatCmdDelay = 3, @@ -268,8 +269,7 @@ mobileChatOpts dbOptions = autoAcceptFileSize = 0, muteNotifications = True, markRead = False, - createBot = Nothing, - maintenance = True + createBot = Nothing } defaultMobileConfig :: ChatConfig diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index afc01e0493..2034b89b9e 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -50,8 +50,7 @@ data ChatOpts = ChatOpts autoAcceptFileSize :: Integer, muteNotifications :: Bool, markRead :: Bool, - createBot :: Maybe CreateBotOpts, - maintenance :: Bool + createBot :: Maybe CreateBotOpts } data CoreChatOpts = CoreChatOpts @@ -68,7 +67,8 @@ data CoreChatOpts = CoreChatOpts deviceName :: Maybe Text, highlyAvailable :: Bool, yesToUpMigrations :: Bool, - migrationBackupPath :: Maybe FilePath + migrationBackupPath :: Maybe FilePath, + maintenance :: Bool } data CreateBotOpts = CreateBotOpts @@ -245,6 +245,12 @@ coreChatOptsP appDir defaultDbName = do <> help "Automatically confirm \"up\" database migrations" ) migrationBackupPath <- migrationBackupPathP + maintenance <- + switch + ( long "maintenance" + <> short 'm' + <> help "Run in maintenance mode (/_start to start chat)" + ) pure CoreChatOpts { dbOptions, @@ -271,7 +277,8 @@ coreChatOptsP appDir defaultDbName = do deviceName, highlyAvailable, yesToUpMigrations, - migrationBackupPath + migrationBackupPath, + maintenance } where useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p @@ -376,12 +383,6 @@ chatOptsP appDir defaultDbName = do ( long "create-bot-allow-files" <> help "Flag for created bot to allow files (only allowed together with --create-bot option)" ) - maintenance <- - switch - ( long "maintenance" - <> short 'm' - <> help "Run in maintenance mode (/_start to start chat)" - ) pure ChatOpts { coreOptions, @@ -400,8 +401,7 @@ chatOptsP appDir defaultDbName = do Just botDisplayName -> Just CreateBotOpts {botDisplayName, allowFiles = createBotAllowFiles} Nothing | createBotAllowFiles -> error "--create-bot-allow-files option requires --create-bot-name option" - | otherwise -> Nothing, - maintenance + | otherwise -> Nothing } parseProtocolServers :: ProtocolTypeI p => ReadM [ProtoServerWithAuth p] diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 5721efb65e..a54a3a6913 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1133,7 +1133,7 @@ getGroupInvitation db vr user groupId = createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> SubscriptionMode -> ExceptT StoreError IO GroupMember createNewContactMember _ _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ _ _ = throwError $ SEContactNotReady localDisplayName createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile, activeConn = Just Connection {connChatVersion, peerChatVRange}} memberRole agentConnId connRequest subMode = - createWithRandomId' gVar $ \memId -> runExceptT $ do + createWithRandomId' db gVar $ \memId -> runExceptT $ do createdAt <- liftIO getCurrentTime member@GroupMember {groupMemberId} <- createMember_ (MemberId memId) createdAt void $ liftIO $ createMemberConnection_ db userId groupMemberId agentConnId connChatVersion peerChatVRange Nothing 0 createdAt subMode @@ -1188,7 +1188,7 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> VersionChat -> VersionRangeChat -> SubscriptionMode -> ExceptT StoreError IO () createNewContactMemberAsync db gVar user@User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) chatV peerChatVRange subMode = - createWithRandomId' gVar $ \memId -> runExceptT $ do + createWithRandomId' db gVar $ \memId -> runExceptT $ do createdAt <- liftIO getCurrentTime insertMember_ (MemberId memId) createdAt groupMemberId <- liftIO $ insertedRowId db @@ -1233,7 +1233,7 @@ createJoiningMember "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" (displayName, fullName, shortDescr, image, contactLink, userId, preferences, currentTs, currentTs) profileId <- liftIO $ insertedRowId db - createWithRandomId' gVar $ \memId -> runExceptT $ do + createWithRandomId' db gVar $ \memId -> runExceptT $ do insertMember_ ldn profileId (MemberId memId) currentTs groupMemberId <- liftIO $ insertedRowId db pure (groupMemberId, MemberId memId) @@ -1318,7 +1318,7 @@ createBusinessRequestGroup VersionRange minV maxV = cReqChatVRange insertClientMember_ currentTs groupId membership = ExceptT . withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do - createWithRandomId' gVar $ \memId -> runExceptT $ do + createWithRandomId' db gVar $ \memId -> runExceptT $ do indexInGroup <- getUpdateNextIndexInGroup_ db groupId liftIO $ DB.execute @@ -1950,7 +1950,7 @@ getMatchingMemberContacts db vr user@User {userId} GroupMember {memberProfile = createSentProbe :: DB.Connection -> TVar ChaChaDRG -> UserId -> ContactOrMember -> ExceptT StoreError IO (Probe, Int64) createSentProbe db gVar userId to = - createWithRandomBytes 32 gVar $ \probe -> do + createWithRandomBytes db 32 gVar $ \probe -> do currentTs <- getCurrentTime let (ctId, gmId) = contactOrMemberIds to DB.execute diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 71e8a35386..ce03edbdbd 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -174,6 +174,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, MsgMeta (..), UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow) +import Simplex.Messaging.Agent.Store.Common (withSavepoint) import Simplex.Messaging.Agent.Store.DB (BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB import qualified Simplex.Messaging.Crypto as C @@ -219,7 +220,7 @@ deleteGroupChatItemsMessages db User {userId} GroupInfo {groupId} = do createNewSndMessage :: MsgEncodingI e => DB.Connection -> TVar ChaChaDRG -> ConnOrGroupId -> ChatMsgEvent e -> (SharedMsgId -> EncodedChatMessage) -> ExceptT StoreError IO SndMessage createNewSndMessage db gVar connOrGroupId chatMsgEvent encodeMessage = - createWithRandomId' gVar $ \sharedMsgId -> + createWithRandomId' db gVar $ \sharedMsgId -> case encodeMessage (SharedMsgId sharedMsgId) of ECMLarge -> pure $ Left SELargeMsg ECMEncoded msgBody -> do @@ -2700,12 +2701,14 @@ updateGroupCIMentions :: DB.Connection -> GroupInfo -> ChatItem 'CTGroup d -> Ma updateGroupCIMentions db g ci@ChatItem {mentions} mentions' | mentions' == mentions = pure ci | otherwise = do - unless (null mentions) $ deleteMentions + unless (null mentions) deleteMentions if null mentions' then pure ci else -- This is a fallback for the error that should not happen in practice. -- In theory, it may happen in item mentions in database are different from item record. - createMentions `E.catch` \e -> if constraintError e then deleteMentions >> createMentions else E.throwIO e + withSavepoint db "create_mentions" createMentions >>= \case + Right r -> pure r + Left e -> if constraintError e then deleteMentions >> createMentions else E.throwIO e where deleteMentions = DB.execute db "DELETE FROM chat_item_mentions WHERE chat_item_id = ?" (Only $ chatItemId' ci) createMentions = createGroupCIMentions db g ci mentions' diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index c9dc87b5fa..3a89bae47d 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -40,6 +40,7 @@ import Simplex.Chat.Types.UITheme import Simplex.Messaging.Agent.Protocol (AConnShortLink (..), AConnectionRequestUri (..), ACreatedConnLink (..), ConnId, ConnShortLink, ConnectionRequestUri, CreatedConnLink (..), UserId, connMode) import Simplex.Messaging.Agent.Store (AnyStoreError (..)) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) +import Simplex.Messaging.Agent.Store.Common (withSavepoint) import Simplex.Messaging.Agent.Store.DB (BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB import qualified Simplex.Messaging.Crypto as C @@ -597,42 +598,23 @@ withLocalDisplayName db userId displayName action = getLdnSuffix >>= (`tryCreate |] (ldn, displayName, ldnSuffix, userId, ts, ts) --- Execute an action within a savepoint (PostgreSQL only). --- On success, releases the savepoint. On error, rolls back to the savepoint --- to restore the transaction to a usable state before returning the error. -withSavepoint :: DB.Connection -> Query -> IO a -> IO (Either SQLError a) -withSavepoint db name action = -#if defined(dbPostgres) - do - DB.execute_ db $ "SAVEPOINT " <> name - E.try action >>= \case - Right r -> do - DB.execute_ db $ "RELEASE SAVEPOINT " <> name - pure $ Right r - Left e -> do - DB.execute_ db $ "ROLLBACK TO SAVEPOINT " <> name - pure $ Left e -#else - E.try action -#endif +createWithRandomId :: forall a. DB.Connection -> TVar ChaChaDRG -> (ByteString -> IO a) -> ExceptT StoreError IO a +createWithRandomId db = createWithRandomBytes db 12 -createWithRandomId :: forall a. TVar ChaChaDRG -> (ByteString -> IO a) -> ExceptT StoreError IO a -createWithRandomId = createWithRandomBytes 12 +createWithRandomId' :: forall a. DB.Connection -> TVar ChaChaDRG -> (ByteString -> IO (Either StoreError a)) -> ExceptT StoreError IO a +createWithRandomId' db = createWithRandomBytes' db 12 -createWithRandomId' :: forall a. TVar ChaChaDRG -> (ByteString -> IO (Either StoreError a)) -> ExceptT StoreError IO a -createWithRandomId' = createWithRandomBytes' 12 +createWithRandomBytes :: forall a. DB.Connection -> Int -> TVar ChaChaDRG -> (ByteString -> IO a) -> ExceptT StoreError IO a +createWithRandomBytes db size gVar create = createWithRandomBytes' db size gVar (fmap Right . create) -createWithRandomBytes :: forall a. Int -> TVar ChaChaDRG -> (ByteString -> IO a) -> ExceptT StoreError IO a -createWithRandomBytes size gVar create = createWithRandomBytes' size gVar (fmap Right . create) - -createWithRandomBytes' :: forall a. Int -> TVar ChaChaDRG -> (ByteString -> IO (Either StoreError a)) -> ExceptT StoreError IO a -createWithRandomBytes' size gVar create = tryCreate 3 +createWithRandomBytes' :: forall a. DB.Connection -> Int -> TVar ChaChaDRG -> (ByteString -> IO (Either StoreError a)) -> ExceptT StoreError IO a +createWithRandomBytes' db size gVar create = tryCreate 3 where tryCreate :: Int -> ExceptT StoreError IO a tryCreate 0 = throwError SEUniqueID tryCreate n = do id' <- liftIO $ encodedRandomBytes gVar size - liftIO (E.try $ create id') >>= \case + liftIO (withSavepoint db "create_random_id" (create id')) >>= \case Right x -> liftEither x Left e | constraintError e -> tryCreate (n - 1) diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 8408fc0098..1fd9f340b2 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -117,8 +117,7 @@ testOpts = autoAcceptFileSize = 0, muteNotifications = True, markRead = True, - createBot = Nothing, - maintenance = False + createBot = Nothing } testCoreOpts :: CoreChatOpts @@ -152,7 +151,8 @@ testCoreOpts = deviceName = Nothing, highlyAvailable = False, yesToUpMigrations = False, - migrationBackupPath = Nothing + migrationBackupPath = Nothing, + maintenance = False } #if !defined(dbPostgres) @@ -303,7 +303,7 @@ insertUser st = withTransaction st (`DB.execute_` "INSERT INTO users (user_id) V #endif startTestChat_ :: TestParams -> ChatDatabase -> ChatConfig -> ChatOpts -> User -> IO TestCC -startTestChat_ TestParams {printOutput} db cfg opts@ChatOpts {maintenance} user = do +startTestChat_ TestParams {printOutput} db cfg opts@ChatOpts {coreOptions = CoreChatOpts {maintenance}} user = do t <- withVirtualTerminal termSettings pure ct <- newChatTerminal t opts cc <- newChatController db (Just user) cfg opts False diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index a56ad6d4e3..3a57aea02f 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1352,7 +1352,7 @@ testNegotiateCall = testMaintenanceMode :: HasCallStack => TestParams -> IO () testMaintenanceMode ps = do withNewTestChat ps "bob" bobProfile $ \bob -> do - withNewTestChatOpts ps testOpts {maintenance = True} "alice" aliceProfile $ \alice -> do + withNewTestChatOpts ps testOpts {coreOptions = testCoreOpts {maintenance = True}} "alice" aliceProfile $ \alice -> do alice ##> "/c" alice <## "error: chat not started" alice ##> "/_start" @@ -1397,7 +1397,7 @@ testChatWorking alice bob = do testMaintenanceModeWithFiles :: HasCallStack => TestParams -> IO () testMaintenanceModeWithFiles ps = withXFTPServer $ do withNewTestChat ps "bob" bobProfile $ \bob -> do - withNewTestChatOpts ps testOpts {maintenance = True} "alice" aliceProfile $ \alice -> do + withNewTestChatOpts ps testOpts {coreOptions = testCoreOpts {maintenance = True}} "alice" aliceProfile $ \alice -> do alice ##> "/_start" alice <## "chat started" alice ##> "/_files_folder ./tests/tmp/alice_files" @@ -1655,7 +1655,7 @@ testSubscribeAppNSE :: HasCallStack => TestParams -> IO () testSubscribeAppNSE ps = withNewTestChat ps "bob" bobProfile $ \bob -> do withNewTestChat ps "alice" aliceProfile $ \alice -> do - withTestChatOpts ps testOpts {maintenance = True} "alice" $ \nseAlice -> do + withTestChatOpts ps testOpts {coreOptions = testCoreOpts {maintenance = True}} "alice" $ \nseAlice -> do alice ##> "/_app suspend 1" alice <## "ok" alice <## "chat suspended" diff --git a/website/src/_data/languages.json b/website/src/_data/languages.json index 0736597953..362e8c4120 100644 --- a/website/src/_data/languages.json +++ b/website/src/_data/languages.json @@ -14,6 +14,7 @@ "textColor": "white", "iconBg": "green", "enabled": true, + "home": true, "rtl": true }, { diff --git a/website/src/_includes/navbar.html b/website/src/_includes/navbar.html index 2a75a2769b..003c069d9c 100644 --- a/website/src/_includes/navbar.html +++ b/website/src/_includes/navbar.html @@ -7,8 +7,8 @@ {% endfor %}