[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Environment (please complete the following information):**\n - OS & version: [e.g. Windows 10 1806]\n - Redis-Server version [e.g. 5.0.1]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n"
  },
  {
    "path": ".github/workflows/run_tests.yml",
    "content": "name: Run Tests\n\non:\n  push:\n    branches:\n    - 2022\n  pull_request:\n    branches: [ 2022 ]\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: checkout\n      uses: actions/checkout@v2.3.4\n      with:\n        submodules: 'recursive'\n    - name: Install Qt\n      uses: jurplel/install-qt-action@v2.13.2\n      with:\n        version: 5.15.2\n        modules: qtcharts\n    - name: Install Build deps\n      run: |\n          sudo apt-get update -y\n          sudo apt-get install cmake liblz4-dev libzstd-dev libbrotli-dev libsnappy-dev lcov -y\n          cmake --version\n          gcc --version\n    - name: Setup Redis\n      uses: zhulik/redis-action@1.1.0\n    - name: Build Tests\n      run: qmake \"SYSTEM_LZ4=1\" \"SYSTEM_ZSTD=1\" \"SYSTEM_SNAPPY=1\" \"SYSTEM_BROTLI=1\" DEFINES+=INTEGRATION_TESTS && make -j 2\n      working-directory: ./tests\n    - name: Run Cpp Tests\n      run: ./../bin/tests/tests -platform minimal -txt\n      working-directory: ./tests  \n    - name: Run QML Tests\n      run: ./../bin/tests/qml_tests -platform minimal -txt\n      working-directory: ./tests\n\n"
  },
  {
    "path": ".github/workflows/sonar.yml",
    "content": "name: Sonar Scan\non:\n  push:\n    branches:\n      - 2022\n  pull_request:\n    types: [opened, synchronize, reopened]\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    env:\n      SONAR_SCANNER_VERSION: 4.6.1.2450 # Find the latest version in the \"Linux\" link on this page:\n                                        # https://sonarcloud.io/documentation/analysis/scan/sonarscanner/\n      SONAR_SERVER_URL: \"https://sonarcloud.io\"\n      BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n          submodules: 'recursive'\n      - name: Install Qt\n        uses: jurplel/install-qt-action@v2.13.2\n        with:\n          version: 5.15.2\n          modules: qtcharts\n      - name: Install system deps\n        run: |\n          sudo apt-get update -y\n          sudo apt-get install cmake liblz4-dev libzstd-dev libbrotli-dev libsnappy-dev -y\n          cmake --version\n          gcc --version\n      - name: Set up JDK 11\n        uses: actions/setup-java@v1\n        with:\n          java-version: 11\n      - name: Cache SonarCloud packages\n        uses: actions/cache@v1\n        with:\n          path: ~/.sonar/cache\n          key: ${{ runner.os }}-sonar\n          restore-keys: ${{ runner.os }}-sonar\n      - name: Download and set up sonar-scanner\n        env:\n          SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip\n        run: |\n          mkdir -p $HOME/.sonar\n          curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}\n          unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/\n          echo \"$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin\" >> $GITHUB_PATH\n      - name: Download and set up build-wrapper\n        env:\n          BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip\n        run: |\n          curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}\n          unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/\n          echo \"$HOME/.sonar/build-wrapper-linux-x86\" >> $GITHUB_PATH\n      - name: Run build-wrapper\n        working-directory: ./src\n        run: |\n          qmake \"SYSTEM_LZ4=1\" \"SYSTEM_ZSTD=1\" \"SYSTEM_SNAPPY=1\" \"SYSTEM_BROTLI=1\"\n          build-wrapper-linux-x86-64 --out-dir ../${{ env.BUILD_WRAPPER_OUT_DIR }} make -j2\n      - name: Run sonar-scanner\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n        run: |\n          sonar-scanner --define sonar.host.url=\"${{ env.SONAR_SERVER_URL }}\" --define sonar.cfamily.build-wrapper-output=\"${{ env.BUILD_WRAPPER_OUT_DIR }}\"\n"
  },
  {
    "path": ".gitignore",
    "content": "*.user\n*.aps\n*.pch\n*.vspscc\n*_i.c\n*_p.c\n*.ncb\n*.suo\n*.bak\n*.cache\n*.ilk\n*.log\n[Bb]in\n[Dd]ebug*/\n*.sbr\nobj/\n[Rr]elease*/\n_ReSharper*/\n*.sdf\n*.opensdf\n*/GeneratedFiles/*\nbuild-redis*\n\n.vagrant/*\nredis-desktop-manager/Makefile*\nbuild/redis*\nbuild/gbreakpad*\nredis-desktop-manager/connections.xml\n*.deb\ndeps/libssh/example/.deps/*\ndeps/libssh/example/.*\ndeps/libssh/example/*\ndeps/libssh/config.status\ndeps/libssh/docs/Makefile\ndeps/libssh/libssh2.pc\ndeps/libssh/libtool\ndeps/libssh/Makefile\ndeps/libssh/src/.*\ndeps/libssh/*.lo\ndeps/libssh/src/**.o\ndeps/libssh/*.lo\ndeps/libssh/*/*.*o\ndeps/libssh/*/*.la\ndeps/libssh/tests/*\ndeps/libssh/src/libssh2_config.h\ndeps/libssh/src/Makefile\ndeps/libssh/src/stamp-h1\nbuild-tests-*/*\ntests/Makefile\ntests/qml_tests/target_wrapper.sh\ndeps/jsoncpp/buildscons/*\ndeps/jsoncpp/dist/*\ndeps/jsoncpp/libs/*\nredis-desktop-manager*.gz\nbuild/cpp-coveralls/*\nbuild/requests/*\n.svn/\ndeps/gyp*\n*.vsp\n*.psess\nbuild/windows/installer/redis-desktop-manager*.exe\nvagrant-provision/automake*\nRDM.sln.metaproj*\ncrashreports/*\nredis-desktop-manager/ui_*.h\nsrc/ui_*.h\nsrc/Makefile*\nbuild-rdm-*\nsrc/rdm.pro.*\ntests/tests.pro.*\n.idea*\n*~\n*Makefile\n*.DS_Store\ntests/unit_tests/coverage*\n*.qmlc\n*.jsc\n.qmake.stash\nparts\nprime\nstage\n3rdparty/python*\n__pycache__/\nqml_*.cpp\nsrc/modules/extension-server/server*\n.openapi-generator*\nbuild-*\n*.xcodeproj\n.xcode\n*qmlcache*\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"3rdparty/qredisclient\"]\n\tpath = 3rdparty/qredisclient\n\turl = https://github.com/uglide/qredisclient.git\n[submodule \"3rdparty/pyotherside\"]\n\tpath = 3rdparty/pyotherside\n\turl = https://github.com/uglide/pyotherside.git\n[submodule \"3rdparty/lz4\"]\n\tpath = 3rdparty/lz4\n\turl = https://github.com/lz4/lz4.git\n[submodule \"3rdparty/simdjson\"]\n\tpath = 3rdparty/simdjson\n\turl = https://github.com/simdjson/simdjson.git\n[submodule \"3rdparty/zstd\"]\n\tpath = 3rdparty/zstd\n\turl = https://github.com/facebook/zstd.git\n[submodule \"3rdparty/snappy\"]\n\tpath = 3rdparty/snappy\n\turl = https://github.com/google/snappy.git\n[submodule \"3rdparty/brotli\"]\n\tpath = 3rdparty/brotli\n\turl = https://github.com/google/brotli.git\n[submodule \"3rdparty/fakeit\"]\n\tpath = 3rdparty/fakeit\n\turl = https://github.com/eranpeer/FakeIt.git\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "version: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-20.04\n  tools:\n    python: \"3.9\"\n\nmkdocs:\n  configuration: mkdocs.yml\n\n# Optionally declare the Python requirements required to build your docs\npython:\n   install:\n   - requirements: docs/requirements.txt"
  },
  {
    "path": "3rdparty/3rdparty.pri",
    "content": "#-------------------------------------------------\r\n#\r\n# Redis Desktop Manager Dependencies\r\n#\r\n#-------------------------------------------------\r\n\r\nexists( $$_PRO_FILE_PWD_/modules/extension-server/client/client.pri) {\r\n    message(\"RESP.app Extension server integration was enabled\")\r\n    DEFINES += ENABLE_EXTERNAL_FORMATTERS\r\n    HEADERS += $$_PRO_FILE_PWD_/modules/extension-server/dataformattermanager.h\r\n    SOURCES += $$_PRO_FILE_PWD_/modules/extension-server/dataformattermanager.cpp\r\n    include($$_PRO_FILE_PWD_/modules/extension-server/client/client.pri)\r\n}\r\n\r\n# qredisclient\r\nif(win32*):exists( $$PWD/qredisclient/qredisclient.lib ) {\r\n    message(\"Using prebuilt qredisclient\")    \r\n    INCLUDEPATH += $$PWD/qredisclient/src/\r\n    OPENSSL_LIB_PATH = C:\\OpenSSL-Win64\\lib\\VC\r\n    LIBS += -L$$OPENSSL_LIB_PATH -llibeay32MD -L$$PWD/qredisclient/ -lqredisclient -lbotan -llibssh2 -lgdi32 -lws2_32 -lkernel32 -luser32 -lshell32 -luuid -lole32 -ladvapi32\r\n    include($$PWD/qredisclient/3rdparty/asyncfuture/asyncfuture.pri)\r\n} else:unix*:exists( $$PWD/qredisclient/libqredisclient.a ) {\r\n    message(\"Using prebuilt qredisclient\")\r\n    INCLUDEPATH += $$PWD/qredisclient/src/\r\n    LIBS += -L$$PWD/qredisclient/ -lqredisclient -lbotan-2 -lssh2 -lz -lssl -lcrypto\r\n    include($$PWD/qredisclient/3rdparty/asyncfuture/asyncfuture.pri)\r\n} else {\r\n    message(\"Using qredisclient source code\")\r\n    include($$PWD/qredisclient/qredisclient.pri)\r\n}\r\n\r\n\r\n#PyOtherSide\r\ninclude($$PWD/pyotherside.pri)\r\n\r\n#LZ4\r\nLZ4DIR = $$PWD/lz4/\r\nINCLUDEPATH += $$LZ4DIR/lib\r\n\r\n#ZSTD\r\nZSTDDIR = $$PWD/zstd/\r\nINCLUDEPATH += $$ZSTDDIR/lib\r\n\r\n#Snappy\r\nSNAPPYDIR = $$PWD/snappy\r\nINCLUDEPATH += $$SNAPPYDIR\r\n\r\n#Brotli\r\nBROTLIDIR = $$PWD/brotli\r\nINCLUDEPATH += $$BROTLIDIR/c/include\r\n\r\n#SIMDJSON\r\nSIMDJSONDIR = $$PWD/simdjson/singleheader\r\nINCLUDEPATH += $$SIMDJSONDIR/\r\nHEADERS += $$SIMDJSONDIR/simdjson.h\r\nSOURCES += $$SIMDJSONDIR/simdjson.cpp\r\n\r\n\r\nwin32* {\r\n    ZLIBDIR = $$PWD/zlib-msvc14-x64.1.2.11.7795/build/native    \r\n    INCLUDEPATH += $$ZLIBDIR/include\r\n    LIBS += $$ZLIBDIR/lib_release/zlibstatic.lib $$LZ4DIR/build/cmake/Release/lz4.lib\r\n    LIBS += $$ZSTDDIR/build/cmake/lib/Release/zstd_static.lib\r\n    LIBS += $$SNAPPYDIR/Release/snappy.lib\r\n    LIBS += -L$$BROTLIDIR/Release/ -lbrotlicommon-static -lbrotlidec-static -lbrotlienc-static\r\n}\r\n\r\nunix:macx { # OSX\r\n    LIBS += -lz $$LZ4DIR/build/cmake/liblz4.a $$ZSTDDIR/build/cmake/lib/libzstd.a\r\n    LIBS += $$SNAPPYDIR/libsnappy.a\r\n    LIBS += -L$$BROTLIDIR/ -lbrotlicommon-static -lbrotlidec-static -lbrotlienc-static\r\n}\r\n\r\nunix:!macx { # ubuntu & debian   \r\n    defined(CLEAN_RPATH, var) { # clean default flags\r\n        message(\"DEB package build\")\r\n        QMAKE_LFLAGS_RPATH=\r\n        QMAKE_LFLAGS = -Wl,-rpath=\\\\\\$$ORIGIN/../lib\r\n        QMAKE_LFLAGS += -static-libgcc -static-libstdc++\r\n    } else {\r\n        # Note: uncomment if qtcreator fails to find QtCore dependencies\r\n        #QMAKE_LFLAGS = -Wl,-rpath=/home/user/Qt5.9.3/5.9.3/gcc_64/lib\r\n    }\r\n\r\n    LIBS += -lz\r\n    defined(SYSTEM_LZ4, var) {\r\n        LIBS += -llz4\r\n    } else {\r\n        LIBS += $$LZ4DIR/build/cmake/liblz4.a\r\n    }\r\n\r\n    defined(SYSTEM_ZSTD, var) {\r\n        LIBS += -lzstd\r\n    } else {\r\n        LIBS += $$ZSTDDIR/build/cmake/lib/libzstd.a\r\n    }\r\n\r\n    defined(SYSTEM_SNAPPY, var) {\r\n        LIBS += -lsnappy\r\n    } else {\r\n        LIBS += $$SNAPPYDIR/libsnappy.a\r\n    }\r\n\r\n    defined(SYSTEM_BROTLI, var) {\r\n        LIBS += -lbrotlicommon -lbrotlidec -lbrotlienc\r\n    } else {\r\n        LIBS += -L$$BROTLIDIR/ -lbrotlienc-static -lbrotlicommon-static -lbrotlidec-static\r\n    }\r\n\r\n    # Unix signal watcher\r\n    defined(LINUX_SIGNALS, var) {\r\n        message(\"Build with qt-unix-signals\")\r\n\r\n        DEFINES += LINUX_SIGNALS\r\n        HEADERS += $$PWD/qt-unix-signals/sigwatch.h\r\n        SOURCES += $$PWD/qt-unix-signals/sigwatch.cpp\r\n        INCLUDEPATH += $$PWD/qt-unix-signals/\r\n    }\r\n}\r\n"
  },
  {
    "path": "3rdparty/pyotherside.pri",
    "content": "\n# Python\nPY_VERSION=\"39\"\nPY_WIN_VERSION=\"38\"\nPY_LIB_SUFFIX=\"3.9\"\n\nwin32* {\n    QMAKE_LIBS += -LC:\\Python$${PY_WIN_VERSION}-x64\\libs -lpython$${PY_WIN_VERSION}\n    INCLUDEPATH += C:\\Python$${PY_WIN_VERSION}-x64\\include\\\n} else {\n    unix:macx {\n      exists($$PWD/python-3) {\n        message(\"Using Python from 3rdparty dir\")\n        LIBS += $$PWD/python-3/lib/libpython$${PY_LIB_SUFFIX}.dylib\n        INCLUDEPATH += $$PWD/python-3/include/python$${PY_LIB_SUFFIX}\n\n        #deployment\n        PY_DATA_FILES.files = $$PWD/python-3/lib/libpython$${PY_LIB_SUFFIX}.dylib\n        PY_DATA_FILES.path = Contents/Frameworks\n        QMAKE_BUNDLE_DATA += PY_DATA_FILES\n\n      } else {\n       PYTHON_CONFIG = /usr/local/bin/python3-config\n       QMAKE_LIBS += $$system($$PYTHON_CONFIG --ldflags --libs --embed)\n       QMAKE_CXXFLAGS += $$system($$PYTHON_CONFIG --includes)\n      }\n    } else {\n      PYTHON_CONFIG = python3-config\n\n      PYTHON_VERSION = $$str_member($$system(python3 --version), 7, 11)\n      message(\"Python version $$PYTHON_VERSION\")\n\n      versionAtLeast(PYTHON_VERSION, \"3.8.0\") {        \n        QMAKE_LIBS += $$system($$PYTHON_CONFIG --ldflags --libs --embed)\n      } else {\n        QMAKE_LIBS += $$system($$PYTHON_CONFIG --ldflags --libs)\n      }\n\n      QMAKE_CXXFLAGS += $$system($$PYTHON_CONFIG --includes)\n      DEFINES *= HAVE_DLADDR\n    }\n}\n\ninclude(pyotherside/pyotherside.pri)\n\nDEFINES += PYOTHERSIDE_VERSION=\\\\\\\"$${VERSION}\\\\\\\"\n\nDEPENDPATH += $$PWD/pyotherside/src\nINCLUDEPATH += $$PWD/pyotherside/src\n\nPYOTHERSIDE_DIR = $$PWD/pyotherside/src/\n\n# Importer from Qt Resources\nRESOURCES += $$PYOTHERSIDE_DIR/qrc_importer.qrc\n\nHEADERS += $$PYOTHERSIDE_DIR/pythonlib_loader.h\\\n    $$PWD/pyotherside/src/callback.h\nSOURCES += $$PYOTHERSIDE_DIR/pythonlib_loader.cpp\n\n# Python QML Object\nSOURCES += $$PYOTHERSIDE_DIR/qpython.cpp\nHEADERS += $$PYOTHERSIDE_DIR/qpython.h\nSOURCES += $$PYOTHERSIDE_DIR/qpython_worker.cpp\nHEADERS += $$PYOTHERSIDE_DIR/qpython_worker.h\nSOURCES += $$PYOTHERSIDE_DIR/qpython_priv.cpp\nHEADERS += $$PYOTHERSIDE_DIR/qpython_priv.h\nHEADERS += $$PYOTHERSIDE_DIR/python_wrap.h\n\n# Globally Load Python hack\nSOURCES += $$PYOTHERSIDE_DIR/global_libpython_loader.cpp\nHEADERS += $$PYOTHERSIDE_DIR/global_libpython_loader.h\n\n# Reference-counting PyObject wrapper class\nSOURCES += $$PYOTHERSIDE_DIR/pyobject_ref.cpp\nHEADERS += $$PYOTHERSIDE_DIR/pyobject_ref.h\n\n# QObject wrapper class exposed to Python\nSOURCES += $$PYOTHERSIDE_DIR/qobject_ref.cpp\nHEADERS += $$PYOTHERSIDE_DIR/qobject_ref.h\nHEADERS += $$PYOTHERSIDE_DIR/pyqobject.h\n\n# GIL helper\nHEADERS += $$PYOTHERSIDE_DIR/ensure_gil_state.h\n\n# Type System Conversion Logic\nHEADERS += $$PYOTHERSIDE_DIR/converter.h\nHEADERS += $$PYOTHERSIDE_DIR/qvariant_converter.h\nHEADERS += $$PYOTHERSIDE_DIR/pyobject_converter.h\nHEADERS += $$PYOTHERSIDE_DIR/qml_python_bridge.h\n"
  },
  {
    "path": "BACKERS.md",
    "content": "## RDM Backers\n\n1.  peters\n2.  WillPerone\n3.  cblage\n4.  richard.hoogenboom\n5.  rodogu\n6.  markoan\n7.  tomlobato\n8.  sun.ming.77\n9.  Wrhector\n10.  trelsco\n11.  Sai P.S.\n12.  mostly-harmless\n13.  chasm\n14.  Clayton Sayer\n15.  henkvos\n16.  syrusm\n17.  stgogm\n18.  pmercier\n19.  elliots\n20.  Itamar Haber\n21.  Kelson\n22.  linux_china\n23.  mjirby\n24.  cristianobaptista\n25.  Scott Steele\n26.  caywood\n27.  GuRui\n28.  ryanski44\n29.  alex.mirrr\n30.  andrewjknox\n31.  chrisgo\n32.  Rob T.\n33.  chrismckee\n34.  ritxi\n35.  Recumbented\n36.  imesner\n37.  ragboy\n38.  tinou.bao\n39.  dbrugne\n40.  brianberlin\n41.  noocyte\n42.  yu, Wu\n43.  Alejandra\n44.  ne0zen\n45.  Macarun\n46.  Mitch\n47.  STRML\n48.  somebody\n49.  sachinwalia\n50.  Wayne Robinson\n51.  PyYoshi\n52.  JHoffmanME\n53.  sebastian.stanisor\n54.  xurumelous\n55.  nilskp\n56.  science\n57.  cicorias\n58.  BrianLocke\n59.  anoordende\n60.  pablovilas\n61.  runes83\n62.  chentex\n63.  forcer\n64.  ikary\n65.  eduardomcrodrigues\n66.  Christophe Cholot\n67.  mickdelaney\n68.  SwaroopH\n69.  David Jonasson\n70.  dean.mehmet\n71.  lyhdj001\n72.  gary.weng.10\n73.  okachan_0417\n74.  xbtequila\n75.  ducu\n76.  timeblimp\n77.  rduplain\n78.  Salada\n79.  djolaq\n80.  Alric\n81.  patrick\n82.  descipar\n83.  marcin.glenszczyk\n84.  Benni\n85.  ksatirli\n86.  devcrust\n87.  Soheil\n88.  rsafier\n89.  leftis\n90.  Brayyy\n91.  artsard\n92.  irvingswiftj\n93.  KeyManPL\n94.  atierant\n95.  tomascayuelas\n96.  kiyoaki\n97.  Jesper Niedermann\n98.  Jingjie Zheng\n99.  humiaozuzu\n100.  rolfvreijdenberger\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "\r\n## IMPORTANT: HOW TO ADD ISSUES\r\n\r\n* GitHub issues **SHOULD ONLY BE USED to report bugs**, and for DETAILED feature\r\n  requests. Everything else belongs to the [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/uglide/RedisDesktopManager)\r\n\r\n  **PLEASE DO NOT POST GENERAL QUESTIONS** that are not about bugs or suspected\r\n  bugs in the GitHub issues system. We'll be very happy to help you and provide\r\n  all the support in the [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/uglide/RedisDesktopManager) \r\n\r\n### Bug report template:\r\n\r\n\tVersion:\r\n\tEnvironment:\r\n\tRedis Server Version:\r\n\r\n\tSteps to reproduce:\r\n\t1.\r\n\t2.\r\n\t3.\r\n\r\n\tExpected result:\r\n\r\n\tActual Result:\r\n\r\n\r\n### Example of bug report:\r\n\r\n\tVersion: 0.6.2\r\n\tEnvironment: Windows 7 SP1 x64\r\n\tRedis Server Version: 2.8.1\r\n\r\n\tSteps to reproduce:\r\n\t1.Click on RedisDesktopManager.ink\r\n\t2.Click on Add Connection button\t\r\n\r\n\tExpected result: Active dialog window\r\n\r\n\tActual Result: Crash\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "COPYRIGHT",
    "content": "RESP.app (formerly RedisDesktopManager), Cross-platform GUI management tool for Redis®\n\nCopyright 2013-2022, Ihor Malinovskyi.\n\nThe RESP.app is released under the terms of the GNU General Public\nLicense, version 3.\n\nThe RESP.app Project includes files written by third\nparties and used with permission or subject to their respective \nlicense agreements.\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "## RESP.app - GUI for Redis &reg; (Formerly RedisDesktopManager)\n\n### RESP.app is joining forces with Redis to offer the Redis community the best possible developer experience and to increase productivity when developing with Redis.\nPlease read [this blog post](https://redis.com/blog/respapp-joining-redis/) where we share more details, and you can also visit the [FAQ](https://resp.app/faq).\n\n<hr>\n\n![RESP.app screenshot](http://resp.app/static/img/features/all.png?v2021)\n\n\n"
  },
  {
    "path": "build/windows/installer/include/install_vcredist_x64.nsh",
    "content": "!include LogicLib.nsh\n\n!macro InstallVCredist\n  !define VCplus_URL \"https://aka.ms/vs/16/release/VC_redist.x64.exe\"\n\n  ReadRegDWORD $0 HKLM \"SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64\" Bld\n  ${If} $0 >= 27033\n    goto VCInstalled\n  ${Else}\n    goto VCDownload\n  ${EndIf}\n\n  VCDownload:\n  DetailPrint \"Beginning download of VC++ 2015-2019 Redistributable.\"\n  inetc::get /TIMEOUT=30000 ${VCplus_URL} \"$TEMP\\vc_redist.x64.exe\" /END\n  Pop $0\n  DetailPrint \"Result: $0\"\n  StrCmp $0 \"OK\" InstallVCplusplus\n  StrCmp $0 \"cancelled\" VCCanceled\n  inetc::get /TIMEOUT=30000 /NOPROXY ${VCplus_URL} \"$TEMP\\vc_redist.x64.exe\" /END\n  Pop $0\n  DetailPrint \"Result: $0\"\n  StrCmp $0 \"OK\" InstallVCplusplus\n\n  MessageBox MB_ICONEXCLAMATION \"Cannot download VC++ 2015-2019 Redistributable. Please install it manually if you experience any issues: ${VCplus_URL}\"\n  ExecShell open \"${VCplus_URL}\"\n  goto VCInstalled\n\n  InstallVCplusplus:\n  DetailPrint \"Completed download.\"\n  Pop $0\n  ${If} $0 == \"cancel\"\n    MessageBox MB_YESNO|MB_ICONEXCLAMATION \\\n    \"Download cancelled.  Continue Installation?\" \\\n    IDYES VCInstalled IDNO VCCanceled\n  ${EndIf}\n\n  DetailPrint \"Pausing installation while downloaded VC++ installer runs.\"\n  DetailPrint \"Installation could take several minutes to complete.\"\n  ExecWait '$TEMP\\vc_redist.x64.exe /passive /norestart'\n\n  DetailPrint \"Removing VC++ installer.\"\n  Delete \"$TEMP\\vc_redist.x64.exe\"\n\n  DetailPrint \"VC++ installer removed.\"\n  goto VCInstalled\n\nVCCanceled:\n  Abort \"Installation cancelled by user.\"\n\nVCInstalled:\n  Pop $0\n!macroend\n"
  },
  {
    "path": "build/windows/installer/include/nsProcess.nsh",
    "content": "!define nsProcess::FindProcess `!insertmacro nsProcess::FindProcess`\n\n!macro nsProcess::FindProcess _FILE _ERR\n\tnsProcess::_FindProcess /NOUNLOAD `${_FILE}`\n\tPop ${_ERR}\n!macroend\n\n\n!define nsProcess::KillProcess `!insertmacro nsProcess::KillProcess`\n\n!macro nsProcess::KillProcess _FILE _ERR\n\tnsProcess::_KillProcess /NOUNLOAD `${_FILE}`\n\tPop ${_ERR}\n!macroend\n\n!define nsProcess::CloseProcess `!insertmacro nsProcess::CloseProcess`\n\n!macro nsProcess::CloseProcess _FILE _ERR\n\tnsProcess::_CloseProcess /NOUNLOAD `${_FILE}`\n\tPop ${_ERR}\n!macroend\n\n\n!define nsProcess::Unload `!insertmacro nsProcess::Unload`\n\n!macro nsProcess::Unload\n\tnsProcess::_Unload\n!macroend\n"
  },
  {
    "path": "build/windows/installer/include/x64.nsh",
    "content": "; ---------------------\n;       x64.nsh\n; ---------------------\n;\n; A few simple macros to handle installations on x64 machines.\n;\n; RunningX64 checks if the installer is running on x64.\n;\n;   ${If} ${RunningX64}\n;     MessageBox MB_OK \"running on x64\"\n;   ${EndIf}\n;\n; DisableX64FSRedirection disables file system redirection.\n; EnableX64FSRedirection enables file system redirection.\n;\n;   SetOutPath $SYSDIR\n;   ${DisableX64FSRedirection}\n;   File some.dll # extracts to C:\\Windows\\System32\n;   ${EnableX64FSRedirection}\n;   File some.dll # extracts to C:\\Windows\\SysWOW64\n;\n\n!ifndef ___X64__NSH___\n!define ___X64__NSH___\n\n!include LogicLib.nsh\n\n!macro _RunningX64 _a _b _t _f\n  !insertmacro _LOGICLIB_TEMP\n  System::Call kernel32::GetCurrentProcess()i.s\n  System::Call kernel32::IsWow64Process(is,*i.s)\n  Pop $_LOGICLIB_TEMP\n  !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}`\n!macroend\n\n!define RunningX64 `\"\" RunningX64 \"\"`\n\n!macro DisableX64FSRedirection\n\n  System::Call kernel32::Wow64EnableWow64FsRedirection(i0)\n\n!macroend\n\n!define DisableX64FSRedirection \"!insertmacro DisableX64FSRedirection\"\n\n!macro EnableX64FSRedirection\n\n  System::Call kernel32::Wow64EnableWow64FsRedirection(i1)\n\n!macroend\n\n!define EnableX64FSRedirection \"!insertmacro EnableX64FSRedirection\"\n\n!endif # !___X64__NSH___\n"
  },
  {
    "path": "build/windows/installer/installer.nsi",
    "content": "!addincludedir .\\include\r\n!addplugindir .\\plugin\r\n\r\nName \"RESP.app (formerly RedisDesktopManager)\"\r\n\r\nBrandingText \"Open source Developer GUI for Redis\"\r\n\r\nRequestExecutionLevel admin\r\n\r\nSetCompress auto\r\nSetCompressor /SOLID /FINAL lzma\r\nManifestDPIAware true\r\n\r\n# General Symbol Definitions\r\n!define REGKEY \"SOFTWARE\\$(Name)\"\r\n!define COMPANY \"Igor Malinovskiy\"\r\n!define URL resp.app\r\n!define APP_EXE \"resp.exe\"\r\n\r\n# MUI Symbol Definitions\r\n!define MUI_ICON \"..\\..\\..\\src\\resources\\images\\logo.ico\"\r\n!define MUI_FINISHPAGE_NOAUTOCLOSE\r\n!define MUI_FINISHPAGE_RUN $INSTDIR\\${APP_EXE}\r\n!define MUI_UNICON \"..\\..\\..\\src\\resources\\images\\logo.ico\"\r\n!define MUI_WELCOMEFINISHPAGE_BITMAP \".\\images\\main.bmp\"\r\n\r\n# Included files\r\n!include \"nsProcess.nsh\"\r\n!include \"x64.nsh\"\r\n!include \"install_vcredist_x64.nsh\"\r\n!include Sections.nsh\r\n!include MUI2.nsh\r\n\r\n# Variables\r\nVar StartMenuGroup\r\n\r\n# Installer pages\r\n!insertmacro MUI_PAGE_WELCOME\r\n!insertmacro MUI_PAGE_LICENSE ..\\..\\..\\LICENSE\r\n!insertmacro MUI_PAGE_DIRECTORY\r\n!insertmacro MUI_PAGE_INSTFILES\r\n!insertmacro MUI_PAGE_FINISH\r\n!insertmacro MUI_UNPAGE_CONFIRM\r\n!insertmacro MUI_UNPAGE_INSTFILES\r\n\r\n\r\n# Installer languages\r\n!insertmacro MUI_LANGUAGE English\r\n\r\n# Installer attributes\r\nOutFile resp-${VERSION}.exe\r\nInstallDir $PROGRAMFILES64\\RESP_app\r\nCRCCheck on\r\nXPStyle on\r\nShowInstDetails show\r\nVIProductVersion ${VERSION}.0\r\nVIAddVersionKey /LANG=${LANG_ENGLISH} ProductName \"RESP.app (formerly RedisDesktopManager)\"\r\nVIAddVersionKey /LANG=${LANG_ENGLISH} ProductVersion \"${VERSION}\"\r\nVIAddVersionKey /LANG=${LANG_ENGLISH} CompanyName \"${COMPANY}\"\r\nVIAddVersionKey /LANG=${LANG_ENGLISH} CompanyWebsite \"${URL}\"\r\nVIAddVersionKey /LANG=${LANG_ENGLISH} FileVersion \"${VERSION}\"\r\nVIAddVersionKey /LANG=${LANG_ENGLISH} FileDescription \"\"\r\nVIAddVersionKey /LANG=${LANG_ENGLISH} LegalCopyright \"\"\r\nInstallDirRegKey HKLM \"${REGKEY}\" Path\r\nShowUninstDetails show\r\n\r\n\r\n# Installer sections\r\nSection -Main SEC0000\r\n    ${nsProcess::KillProcess} \"rdm.exe\" $R4\r\n    ${nsProcess::KillProcess} \"${APP_EXE}\" $R4\r\n\r\n    ${IfNot} ${RunningX64}\r\n        MessageBox MB_OK \"Starting from version 2019.0.0, RESP.app doesn't support 32-bit Windows\"\r\n        Quit\r\n    ${EndIf}\r\n\r\n    IfFileExists $INSTDIR\\uninstall.exe already_installed not_installed\r\n    already_installed:\r\n    CopyFiles /SILENT /FILESONLY \"$INSTDIR\\uninstall.exe\" \"$INSTDIR\\uninstall_.exe\"\r\n    ExecWait '\"$INSTDIR\\uninstall_.exe\" /S _?=$INSTDIR'\r\n    Sleep 100\r\n    Delete /REBOOTOK $INSTDIR\\uninstall_.exe\r\n\r\n    not_installed:\r\n    SetOutPath $INSTDIR    \r\n    File /r resources\\*\r\n    WriteRegStr HKLM \"${REGKEY}\\Components\" Main 1\r\n    !insertmacro InstallVCredist\r\n    BringToFront\r\nSectionEnd\r\n\r\nSection -post SEC0001\r\n    WriteRegStr HKLM \"${REGKEY}\" Path $INSTDIR\r\n    SetOutPath $INSTDIR\r\n    WriteUninstaller $INSTDIR\\uninstall.exe\r\n    SetOutPath $SMPROGRAMS\\$StartMenuGroup\r\n    \r\n    CreateShortCut \"$DESKTOP\\RESP.lnk\" \"$INSTDIR\\${APP_EXE}\" \"\"\r\n    \r\n    IfSilent 0 +2\r\n        Exec \"$INSTDIR\\${APP_EXE}\"\r\n\r\n    CreateShortcut \"$SMPROGRAMS\\$StartMenuGroup\\RESP.lnk\" \"$INSTDIR\\${APP_EXE}\"\r\n    CreateShortcut \"$SMPROGRAMS\\$StartMenuGroup\\$(^UninstallLink).lnk\" $INSTDIR\\uninstall.exe\r\n\r\n    WriteRegStr HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" DisplayName \"$(^Name)\"\r\n    WriteRegStr HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" DisplayVersion \"${VERSION}\"\r\n    WriteRegStr HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" Publisher \"${COMPANY}\"\r\n    WriteRegStr HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" URLInfoAbout \"${URL}\"\r\n    WriteRegStr HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" DisplayIcon $INSTDIR\\uninstall.exe\r\n    WriteRegStr HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" UninstallString $INSTDIR\\uninstall.exe\r\n    WriteRegDWORD HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" NoModify 1\r\n    WriteRegDWORD HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\" NoRepair 1\r\nSectionEnd\r\n\r\n# Macro for selecting uninstaller sections\r\n!macro SELECT_UNSECTION SECTION_NAME UNSECTION_ID\r\n    Push $R0\r\n    ReadRegStr $R0 HKLM \"${REGKEY}\\Components\" \"${SECTION_NAME}\"\r\n    StrCmp $R0 1 0 next${UNSECTION_ID}\r\n    !insertmacro SelectSection \"${UNSECTION_ID}\"\r\n    GoTo done${UNSECTION_ID}\r\nnext${UNSECTION_ID}:\r\n    !insertmacro UnselectSection \"${UNSECTION_ID}\"\r\ndone${UNSECTION_ID}:\r\n    Pop $R0\r\n!macroend\r\n\r\n# Uninstaller sections\r\nSection /o -un.Main UNSEC0000\r\n    ${nsProcess::KillProcess} \"${APP_EXE}\" $R4\r\n    Sleep 1000\r\n    Delete /REBOOTOK $INSTDIR\\*\r\n    RmDir /REBOOTOK /r $INSTDIR\\*\r\n    DeleteRegValue HKLM \"${REGKEY}\\Components\" Main\r\nSectionEnd\r\n\r\nSection -un.post UNSEC0001\r\n    DeleteRegKey HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$(^Name)\"\r\n    Delete /REBOOTOK \"$DESKTOP\\RESP.lnk\"\r\n    Delete /REBOOTOK \"$SMPROGRAMS\\$StartMenuGroup\\RESP.lnk\"\r\n    Delete /REBOOTOK \"$SMPROGRAMS\\$StartMenuGroup\\$(^UninstallLink).lnk\"\r\n    Delete /REBOOTOK $INSTDIR\\uninstall.exe\r\n    DeleteRegValue HKLM \"${REGKEY}\" Path\r\n    DeleteRegKey /IfEmpty HKLM \"${REGKEY}\\Components\"\r\n    DeleteRegKey /IfEmpty HKLM \"${REGKEY}\"\r\n    RmDir /REBOOTOK $SMPROGRAMS\\$StartMenuGroup\r\n    RmDir /REBOOTOK $INSTDIR\r\nSectionEnd\r\n\r\n# Installer functions\r\nFunction .onInit\r\n    InitPluginsDir\r\n    StrCpy $StartMenuGroup RESP\r\nFunctionEnd\r\n\r\n# Uninstaller functions\r\nFunction un.onInit\r\n    SetAutoClose true\r\n    ReadRegStr $INSTDIR HKLM \"${REGKEY}\" Path\r\n    StrCpy $StartMenuGroup RESP\r\n    !insertmacro SELECT_UNSECTION Main ${UNSEC0000}\r\nFunctionEnd\r\n\r\n# Installer Language Strings\r\nLangString ^UninstallLink ${LANG_ENGLISH} \"Uninstall $(^Name)\"\r\n"
  },
  {
    "path": "build/windows/installer/resources/qt.conf",
    "content": "[Paths]\nPrefix=..\n\n"
  },
  {
    "path": "docs/app-store.md",
    "content": "## Limitations of App Store version\n\n* AppStore version of RESP.app doesn't support [Native Formatters](native-formatters.md)\n  \n  \n"
  },
  {
    "path": "docs/bulk-operations.md",
    "content": "# Bulk operations\n***\n\nRESP.app simplifies your Redis daily routines with bulk operations. To access bulk operations connect to Redis\nserver and click on a target database like db0:\n\n<img src=\"http://resp.app/static/docs/bulk_operations.png?v=1\" width=\"350\" />\n\n## Supported bulk operations\n\n<img src=\"http://resp.app/static/docs/bulk_operations_list.png?v=1\" width=\"350\" />\n\n### Flush database\n\nIt's a useful operation if you need to invalidate cache in a couple clicks instead of firing `FLUSHDB` command.\n> !!! warning \"Be careful\"\n    Do not use it on Production servers. You can safeguard your Production Redis server by using [a restricted user with limited permissions](https://redis.io/docs/manual/security/acl/).\n\n### Delete keys with filter\n\nIf you need to remove some specific keys or a [\"namespace\"](lg-keyspaces.md#use-namespaced-keys) from your Redis server this bulk operation can come in handy.\nIt allows you to specify a glob style pattern to define which keys should be removed.\n\n![](http://resp.app/static/docs/bulk_delete_keys.png?v=1)\n\n### Set TTL for multiple keys\n\nAs you know, Redis is an in-memory database. You should be careful and set appropriate TTL for all keys otherwise Redis can \ncrash or stop responding after consuming all available memory. If you realized that some keys have wrong TTL values or don't have TTL at all you can fix it using RESP.app:\n\n![](http://resp.app/static/docs/bulk_ttl.png?v=1)\n\n\n### Copy keys from one Redis server to another\n\nSometimes you need to copy some keys from a Production Redis server to local one for debugging or vice-versa.\nYou can achieve that by writing custom script, however it's much easier to just make a couple of clicks in RESP.app to copy keys:\n\n> !!! warning \"Limitations\"\n    Currently RESP.app supports only copying data between redis-servers with the same RDB version. \n    Usually it means that major versions of both Redis servers should be the same.  \n\n![](http://resp.app/static/docs/bulk_copy_keys.png?v=1)\n\n\n### Import keys directly from RDB files\n\nUsually, production Redis servers have [AOF or RDB back-ups or persistent files](https://redis.io/docs/manual/persistence/).\nWhile AOF is basically a file with all commands that should be played again to reconstruct original dataset, RDB files don't have such flexibility.\nTherefore, RESP.app provides a convenient way to easily import subset of data for debugging and testing directly from RDB file instead of creating additional load to your Production system.\n\n![](http://resp.app/static/docs/bulk_import_rdb.png?v=1)\n\n\n#### Is your use case not covered in RESP.app? [Contact us](mailto:support@resp.app), and we will do our best to solve it!  \n"
  },
  {
    "path": "docs/css/extra.css",
    "content": "img {\n   max-height: 500px;\n}\n\ncode {\n font-size: 11pt;\n}"
  },
  {
    "path": "docs/development.md",
    "content": "### Build RESP.app from source\nSee [instruction](install.md#build-from-source)\n\n### Generate test data\nOpen RESP.app console or redis-cli and execute:\n\n```lua\neval \"for index = 0,100000 do redis.call('SET', 'test_key' .. index, index) end\" 0\neval \"for index = 0,100000 do redis.call('SET', 'test_key:' .. math.random(1, 100) .. ':' .. math.random(1,100), index) end\" 0\neval \"for index = 0,100000 do redis.call('HSET', 'test_large_hash', index, index) end\" 0\neval \"for index = 0,100000 do redis.call('ZADD', 'test_large_zset', index, index) end\" 0\neval \"for index = 0,100000 do redis.call('SADD', 'test_large_set', index) end\" 0\neval \"for index = 0,100000 do redis.call('LPUSH', 'test_large_list', index) end\" 0\n```\n\n### App profiling\n\n```bash\nsudo apt-get install valgrind\nsudo add-apt-repository ppa:kubuntu-ppa/backports \nsudo apt-get update\nsudo apt-get install massif-visualizer\n\nexport LD_LIBRARY_PATH=\"/usr/share/redis-desktop-manager/lib\":$LD_LIBRARY_PATH\nvalgrind --tool=massif --massif-out-file=rdm.massif /usr/share/redis-desktop-manager/bin/rdm\n\n```\n\n### Debug SSL\n```bash\nopenssl s_client -connect HOST:PORT -cert test_user.crt -key test.key -CAfile test_ca.pem\n```\n\n### Remove app settings on OSX\n```bash\nrm $HOME/Library/Preferences/com.redisdesktop.RedisDesktopManager.plist\nkillall -u `whoami` cfprefsd\n```\n\n### Fix bugs or implement whatever you want :)\n"
  },
  {
    "path": "docs/extension-server.md",
    "content": "## RESP.app Extension Server\n\nDevelopers love Redis because it gives freedom to store anything they want in it.\nRESP.app shares this ideology by supporting automatic decompression (GZIP, LZ4, ZSTD, BROTLI, Snappy) and deserialization of common formats like MsgPack, PHP Sessions, CBOR and Pickle.\n\nIs your serialization format not mentioned above? Continue reading to find out how to easily view your data in RESP.app.\n\n### What is it?\n\nStarting from version `2022.4` RESP.app comes with a built-in client for Extension Server. Extension Server is a simple REST API defined by\nthe following [OpenAPI Specification](extension-server.md#openapi-v3-specification). This server allows you to support \nany custom compression or serialization format.\n\n### Build your own Extension Server in minutes\n\nThanks to [OpenAPI Generator](https://openapi-generator.tech/docs/installation) you can generate boilerplate for your Extension Server in a couple of minutes.\n\n1. [Install OpenAPI Generator](https://openapi-generator.tech/docs/installation)\n2. Select [appropriate server generator](https://openapi-generator.tech/docs/generators#server-generators).\n3. Download spec file from `https://raw.githubusercontent.com/uglide/RedisDesktopManager/2022/docs/server_spec.yaml`\n4. Generate server: <br />\n`\nopenapi-generator generate -i server_spec.yaml -g YOUR_GENERATOR -o my_extension_server\n`\n5. Open `my_extension_server` in your favorite IDE and start adding your custom formatters to generated server.\n\n\n**If you are faced with any issues you can [contact support](mailto:support@resp.app) or ask for help in [telegram chat](https://t.me/RedisDesktopManager)**\n\n\n### Connect to Extension Server in RESP.app\n\n1. Ensure that you are using RESP.app version `2022.4` or above\n2. Click on the \"Extension Server\" button in top right corner of the main window\n3. In the Extension Server dialog specify your server URL and basic auth details if any:\n<img src=\"http://resp.app/static/docs/extension-server.png\" width=\"550\" />\n4. Hit Reload button\n\n### Visualizing data with Extension Server\n\nRESP.app supports following `Content-Type` responses from Extension Server:\n\n- `application/json`\n- `image/*` for example `image/svg+xml`\n\nThis allows you to perform any required preprocessing and visualize your data:\n<img src=\"http://resp.app/static/docs/extension-server-chart.png\" width=\"550\" />\n\n\n\n### OpenAPI v3 Specification\n\n**Please submit your proposals to the following spec on [GitHub](https://github.com/uglide/RedisDesktopManager/issues)**\n\n!!swagger server_spec.yaml!!\n\n\n### Third-party extension servers\n\nYou can find some examples on [GitHub](https://github.com/search?q=resp.app+extension+server).\n"
  },
  {
    "path": "docs/faq.md",
    "content": "## Where is the connections config stored?\n\n**Windows** `%USERPROFILE%\\.rdm\\connections.json`\n\n**macOS dmg** `$HOME/Library/Preferences/rdm/connections.json`\n\n**macOS App Store** `$HOME/Library/Containers/com.redisdesktop.rdm/Data/Library/Preferences/rdm/`\n\n**Linux flatpak** `$HOME/.rdm/connections.json`\n\n**Linux snap** `$HOME/snap/redis-desktop-manager/common/.rdm/connections.json`\n"
  },
  {
    "path": "docs/index.md",
    "content": "# RESP.app Documentation\n\nRESP.app (formerly RedisDesktopManager) — is a cross-platform open source GUI for Redis &reg; available on Windows, Linux and macOS. This tool offers you an easy-to-use GUI to access your Redis &reg; DB and perform some basic operations: view keys as a tree, CRUD keys, execute commands via shell. RESP.app supports SSL/TLS encryption, SSH tunnels and cloud Redis instances, such as: Amazon ElastiCache, Microsoft Azure Redis Cache and other Redis &reg; clouds.    \n\n\nPlease submit any issues and proposals on [GitHub](https://github.com/uglide/RedisDesktopManager/issues)\n\n\n\n\n#### Ask for help in [telegram chat](https://t.me/RedisDesktopManager)\n"
  },
  {
    "path": "docs/install.md",
    "content": "# Quick Install\n\n## Windows\n\n1. Install [Microsoft Visual C++ 2015-2019 x64](https://aka.ms/vs/16/release/vc_redist.x64.exe)  (If you have not already).\n2. Download Windows Installer from [http://resp.app/subscriptions](http://resp.app/subscriptions). **(Requires subscription)**\n3. Run the downloaded installer.\n\n## Mac OS X\n\n1. Download dmg image from [http://resp.app/subscriptions](http://resp.app/subscriptions). **(Requires subscription)**\n2. Mount the DMG image.\n3. Run rdm.app.\n\n## Ubuntu / ArchLinux / Debian / Fedora / CentOS / OpenSUSE / etc\n\n### Install flatpak\n\n1. Install RESP.app using [Flathub](https://flathub.org/apps/details/app.resp.RESP).\n\n> !!! info \"How to install in command line\"\n    Make sure to follow the [setup guide](https://flatpak.org/setup/) before installing\n    <br />`flatpak install flathub app.resp.RESP`\n\n> !!! tip \"How to run\"\n    If RESP.app icon hasn't appeared in your application launcher, you can run RESP.app from terminal with:\n    <br /> `flatpak run app.resp.RESP`\n\n### Install snap\n\n1. Install RESP.app using [Snapcraft](https://snapcraft.io/redis-desktop-manager).\n\n> !!! warning \"SSH Keys\"\n    To be able to access your ssh keys from RESP.app please connect `ssh-key` interface:\n    `sudo snap connect redis-desktop-manager:ssh-keys`\n    \n> !!! tip \"How to Run\"\n    If RESP.app icon hasn't appeared in your application launcher you can run RESP.app from terminal `/snap/bin/redis-desktop-manager.rdm`\n\n## Build from source\n\n### Get source\n\n1. Install git using the instructions here: https://git-scm.com/download\n    \n2. Get the source code:\n    ```\n    git clone --recursive https://github.com/uglide/RedisDesktopManager.git -b 2022 rdm && cd ./rdm\n    ```\n\n> !!! warning \"SSH Tunneling support\"\n    Since 0.9.9 RESP.app by default does not include SSH Tunneling support. You can create a SSH tunnel to your Redis server manually and connect to `localhost`:\n    `ssh -L 6379:REDIS_HOST:6379 SSH_USER@SSH_HOST -P SSH_PORT -i SSH_KEY -T -N` or [use pre-built binary for your OS](#quick-install)\n\n\n### Build on OS X\n\n1. Install [Xcode](https://developer.apple.com/xcode/) with Xcode build tools.\n2. Install [Homebrew](http://brew.sh/).\n3. Copy `cd ./src && cp ./resources/Info.plist.sample ./resources/Info.plist`.\n4. Building RESP.app dependencies require i.a. `openssl`, `cmake` and `python3`. Install them: `brew install openssl cmake python3`\n5. Build lz4 lib\n```\ncd 3rdparty/lz4/build/cmake\ncmake -DLZ4_BUNDLED_MODE=ON  .\nmake\ncd 3rdparty/brotli\ncmake -DBUILD_SHARED_LIBS=OFF\nmake\ncd 3rdparty/snappy\ncmake -DHAVE_LIBLZO2=0 -DHAVE_LIBLZ4=0 && make\ncd 3rdparty/zstd/build/cmake\ncmake ./ && make\n```\n6. Install Python requirements `pip3 install -t ../bin/osx/release -r py/requirements.txt`\n7. Install [Qt 5.15](http://www.qt.io/download-open-source/#section-2). Add Qt Creator and under Qt 5.15.x add Qt Charts module.\n8. Open `./src/rdm.pro` in **Qt Creator**.\n9. Run build. \n\n### Build on Windows\n\n1. Install [Visual Studio 2019 Community Edition](https://visualstudio.microsoft.com/vs/).\n2. Install [Qt 5.15](https://www.qt.io/download).\n3. Go to `3rdparty/qredisclient/3rdparty/hiredis` and apply the patch to fix compilation on Windows:\n`git apply ../hiredis-win.patch`\n4. Go to the `3rdparty/` folder and install zlib with `nuget`: `nuget install zlib-msvc14-x64 -Version 1.2.11.7795`\n5. Build lz4 lib\n```\ncd 3rdparty/lz4/build/cmake\ncmake -DLZ4_BUNDLED_MODE=ON  .\nmake\n```\n6. Install Python 3.9 amd64 to `C:\\Python39-x64`.\n7. Install Python requirements `pip3 install -r src/py/requirements.txt`.\n8. Open `./src/rdm.pro` in **Qt Creator**.  Choose the `Desktop Qt 5.15.x MSVC2019 64bit > Release` build profile.\n9. Run build. (Just hit `Ctrl-B`)\n"
  },
  {
    "path": "docs/known-issues.md",
    "content": "### Application looks corrupted on my 1080p screen on Linux (too small font and/or broken dialogs)\nRun RESP.app from terminal without Qt Autoscaling: `Exec=env QT_AUTO_SCREEN_SCALE_FACTOR=0 redis-desktop-manager.rdm`\n"
  },
  {
    "path": "docs/lg-keyspaces.md",
    "content": "# Working with large keyspaces\n\nBy default, RESP.app uses `*` (wildcard glob-style pattern) in  `SCAN` command to load all keys from the selected database. It’s simple and user-friendly for cases when you have only a couple of thousands keys. But for production redis-servers with millions of keys it leads to a huge amount of time needed to load keys in RESP.app.\nOn this page you will find different approaches how to work with large Redis keyspaces efficiently. \n\n## Increase limit for `SCAN` command\nRESP.app limits amount of keys that should be scanned by Redis to `10000`. If you have more than 100K keys in Redis it's recommended to increase this limit to\n`50000` or `100000`. \n\n> !!! warning \"Be careful!\"\n    High scanning limit may affect your Redis performance!\n\nTo increase this limit click on the Settings button in top right corner for the main window and change value for `Limit for SCAN command` setting.\n\n## Use specific `SCAN` filter to reduce loaded amount of keys\n\nConsider using more specific  filters for `SCAN` in order to speed up keys loading and reduce memory footprint \n\n1. Right click on database and click on Filter button <br> <img width=\"250\" src=\"https://user-images.githubusercontent.com/1655867/91542521-aa18c300-e926-11ea-8f09-4a0322d0f9ee.png\">\n2. Enter glob-style pattern and press apply button <br> <img width=\"250\" src=\"https://user-images.githubusercontent.com/1655867/91542549-b4d35800-e926-11ea-9920-ca0ad8701c56.png\">\n\n> !!! note\n    More details about `SCAN` filter syntax you can find in Redis documentation [https://redis.io/commands/scan#the-match-option]()\n\n\nDefault `SCAN` filter can be changed in connection settings on “Advanced Settings” tab:\n<br /><img width=\"500\" src=\"https://user-images.githubusercontent.com/1655867/91543353-1eebfd00-e927-11ea-81ed-90bcc25c41f0.png\">\n\n\n## Use namespaced keys\n\nColon sign `:` is a commonly used convention when naming Redis keys. For example you can use following schema to store information about users:\n\n`user:1000`\n\nFollowing this schema allows you to simplify removal of obsolete keys and performing other operations with keys in Redis.\n\nUsing namespaced keys is also important for loading huge keyspaces in RESP.app. It renders namespaces on demand (since 2020.2+) and this approach allows to visualise millions of keys with small memory footprint.\n\n<img width=\"250\" src=\"https://user-images.githubusercontent.com/1655867/91547979-5c538900-e92d-11ea-8afa-10cc1634343f.png\">\n\nDefault namespace separator can be changed in connection settings on “Advanced Settings” tab.\n\nMore tips about Redis keys naming you can find in this tutorial [https://redis.io/topics/data-types-intro#redis-keys]()\n\n"
  },
  {
    "path": "docs/native-formatters.md",
    "content": "## Native value formatters\n\n> !!! warning \"End of life\"\n    This feature was deprecated and removed from RESP.app. Please use [Extension Server](extension-server.md) instead  \n\n\n\n\n\n"
  },
  {
    "path": "docs/quick-start.md",
    "content": "# **How to start using RESP.app**\n***\n\n\nAfter you've [installed](install.md) RESP.app, the first thing you need to do in order to get going is to create a connection to your Redis server. On the main window, press the button labelled **Connect to Redis Server**. \n\n![](http://resp.app/static/docs/rdm_main.png?v=2)\n\n## Connect to a local or public redis-server\nOn the first tab (Connection Settings), put in general information regarding the connection that you are creating.\n\n* **Name** - the name of new connection (example: my_local_redis)\n* **Host** - redis-server host (example: localhost)\n* **Port** - redis-server port (example: 6379)\n* **Password** - redis-server authentication password (if any) ([http://redis.io/commands/AUTH](http://redis.io/commands/AUTH))\n* **Username** - only for redis-servers >= 6.0 with configured [ACL](https://redis.io/topics/acl), for older redis-server leave empty\n\n## Connect to a public redis-server with SSL\nIf you want to connect to a redis-server instance with SSL you need to enable SSL on the second tab and provide a public key in PEM format. \nInstructions for certain cloud services are below:\n\n<img src=\"http://resp.app/static/docs/rdm_ssl.png?v=2\" />\n\n### AWS ElastiCache\nAWS ElastiCache is not accessible outside of your VPC. In order to connect to your ElastiCache remotely, you need to use one of the following options:\n\n*  Setup VPN connection **[Recommended]**\n[https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/accessing-elasticache.html#access-from-outside-aws](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/accessing-elasticache.html#access-from-outside-aws)\n*  Setup SSH proxying host and connect through SSH tunnel. **[Slow network performance. Not recommended]**\n*  Setup NAT instance for exposing your AWS ElastiCache to the Internet **[Firewall rules should be configured very carefully. Not recommended.]**\n\n#### How to connect to AWS ElastiCache with In-Transit Encryption\n##### VPN / NAT\nEnable SSL/TLS checkbox and connect to your AWS ElastiCache with In-Transit Encryption.\n\n##### SSH tunnel\nClick on \"Enable TLS-over-SSH\" checkbox in the the SSH connection settings and connect to your AWS ElastiCache with In-Transit Encryption.\n\n### Microsoft Azure Redis Cache <br /> <img src=\"https://docs.microsoft.com/en-us/azure/azure-cache-for-redis/media/index/redis-cache.svg\" width=\"100\" />\n\n1. Create a connection with all requested information.\n <br /> <img src=\"http://resp.app/static/docs/rdm_ssl_azure.png?v=2\" />\n2. Make sure that the \"Use SSL Protocol\" checkbox is enabled.\n3. Your Azure Redis connection is ready to use.\n\n### Redis Labs <br /> <img src=\"https://upload.wikimedia.org/wikipedia/commons/7/75/Redis_Labs_Logo.png\" height=\"75\" />\nTo connect to a Redis Labs instance with SSL/TLS encryption, follow the steps below:\n\n1. Make sure that SSL is enabled for your Redis instance in the Redis Labs dashboard.\n2. Download and unzip `garantia_credentials.zip` from the Redis Labs dashboard.\n3. Select `garantia_user.crt` in the \"Public key\" field.\n4. Select `garantia_user_private.key` in the \"Private key\" field.\n5. Select `garantia_ca.pem` in the \"Authority\" field.\n\n### Digital Ocean Managed Redis <br /> <img src=\"https://upload.wikimedia.org/wikipedia/commons/f/ff/DigitalOcean_logo.svg\" width=\"100\">\nDigital Ocean connection settings is a bit confusing. To connect to a Digital Ocean Managed Redis you need to follow steps bellow:\n\n1. Copy host, port and password information to RESP.app\n2. **Leave Username field in RESP.app empty** (Important!)\n3. Enable SSL/TLS checkbox\n\nOr using Quick Connect tab for new connections:\n1. Copy connection string (starts with \"rediss://\") from connection details to RESP.app\n2. Click \"Import\" and \"Test Connection\"\n\n### Heroku Redis <br /> <img src=\"https://brand.heroku.com/static/media/heroku-logo-stroke.aa0b53be.svg\" width=\"80\">\n\n1. Get Redis connection string from terminal with command \n```\nheroku config -a YOUR-APP-NAME | grep REDIS\n```\nor get it from Heroku website.\n\nExample output:\n```\nrediss://user:password@host:port\n```\n2. Enter connection settings in RESP.app Connection dialog:\n- If URL starts with `rediss` enable SSL/TLS checkbox and **uncheck** \"Enable strict mode\" checkbox\n- Copy `user` to \"Username\" field\n- Copy `password` to \"Password\" field\n- Copy `host` and `port` to \"Address\" field in RESP.app\n\n## Connect to private redis-server via SSH tunnel\n### Basic SSH tunneling\nSSH tab is supposed to allow you to use a SSH tunnel. It's useful if your redis-server is not publicly accessible.\nTo use a SSH tunnel select checkbox \"SSH Tunnel\". There are different security options; you can use a plain password or OpenSSH private key. \n\n>!!! note \"for Windows users:\" \n    Your private key must be in .pem format.\n\n<img height=\"350\" src=\"http://resp.app/static/docs/resp_ssh.png?v=1\" />\n\n### SSH Agent\nStarting from version 2022.3 RESP.app supports SSH Agents. This allows using password managers like [1Password](https://developer.1password.com/docs/ssh/agent)\nto securely store your SSH keys with 2FA.\n\n>!!! note \"for Windows users:\" \n    On Windows RESP.app supports only [Microsoft OpenSSH](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_overview) that's why \"Custom SSH Agent Path\" option is not available.  \n\n##### How to connect to 1Password SSH-Agent from DMG version of RESP.app\nIt's possible to set default SSH Agent for all connections in RESP.app by overriding environment variable `SSH_AUTH_SOCK`.\nIf you need to use custom ssh agent only for specific connections follow steps above:\n\n1. Create symlink to agent.sock\n```\nmkdir -p ~/.1password && ln -s ~/Library/Group\\ Containers/2BUA8C4S2C.com.1password/t/agent.sock ~/.1password/agent.sock\n```\n2. In RESP.app check \"Use SSH Agent\" checkbox and click on the \"Select File\" button next to \"Custom SSH Agent Path\" field\n3. Press `⌘ + Shift + .` to show hidden files and folders in the dialog\n4. Select file `~/.1password/agent.sock`\n5. Save connection settings\n\n##### How to connect to SSH-Agent from AppStore version of RESP.app\n\nDue to AppStore sandboxing RESP.app cannot access default or custom SSH Agents defined by `SSH_AUTH_SOCK` variable.\nTo overcome this limitation you need to create proxy unix socket inside RESP.app sandbox container:\n\n1. Install socat with homebrew\n```\nbrew install socat\n```\n2. Create proxy unix-socket with socat:\n```\nsocat UNIX-LISTEN:$HOME/Library/Containers/com.redisdesktop.rdm/Data/agent.sock UNIX-CONNECT:\"$HOME/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock\"\n```\n\n\n### Advanced SSH tunneling\nIf you need advanced SSH tunneling you should setup a SSH tunnel manually and connect via localhost:\n```\nssh SSH_HOST -L 7000:localhost:6379\n```\n\n## Connect to a UNIX socket\n\nRESP.app [doesn't support UNIX sockets](https://github.com/uglide/RedisDesktopManager/issues/1751) directly, but you can use redirecting of the local socket to the UNIX domain socket, for instance with [socat](https://sourceforge.net/projects/socat):\n\n```\nsocat -v tcp-l:6379,reuseaddr,fork unix:/tmp/redis.sock\n```\n\n## Advanced connection settings\nThe **Advanced settings** tab allows you to customise the namespace separator and other advanced settings.\n<img src=\"http://resp.app/static/docs/rdm_advanced_settings.png?v=3\" />\n\n## Next steps\nNow you can test a connection or create a connection right away. \n\nCongratulations, you've successfully connected to your Redis Server. You should see something similar to what we show below.\n![](http://resp.app/static/docs/rdm_main2.png?v=2)\n\n\nClick on the connection and expand keys. By clicking the right button, you can see console menu and manage your connection from there. \n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "mkdocs==1.3.0\nmkdocs-render-swagger-plugin==0.0.3"
  },
  {
    "path": "docs/server_spec.yaml",
    "content": "openapi: 3.0.0\ninfo:\n  version: 2022.0-preview1\n  title: RESP.app Extension server\n  description: RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\npaths:\n  /data-formatters:\n    get:\n      description: Returns a list of data formatters\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:    \n              schema:\n                $ref: \"#/components/schemas/DataFormatters\"\n  /data-formatters/{id}/decode:\n    post:\n      parameters:\n        - name: id\n          in: path\n          required: true\n          description: The id of data formatter\n          schema:\n            type: string\n      requestBody:\n        content: \n          'application/json':\n            schema:\n              $ref: '#/components/schemas/DecodePayload'\n      responses:\n        '200':\n          description: Successful response with correct content type. RESP.app supports text/plain, application/json and application/octet-stream\n          content:\n            '*/*' :\n              schema:\n                type: string\n        '400':\n          description: Validation error response\n          content: \n            'application/json':\n              schema:\n                type: object\n                properties:\n                  error:\n                    type: string\n          \n  /data-formatters/{id}/encode:\n    post:\n      parameters:\n        - name: id\n          in: path\n          required: true\n          description: The id of data formatter\n          schema:\n            type: string\n      requestBody:\n        content: \n          'application/json':\n            schema:\n              $ref: '#/components/schemas/EncodePayload'\n      responses:\n        '200':\n          description: Successful response with content type application/octet-stream\n          content:\n            '*/*' :\n              schema:\n                type: string\n        '400':\n          description: Validation error response\n          content: \n            'application/json':\n              schema:\n                type: object\n                properties:\n                  error:\n                    type: string        \ncomponents:\n  securitySchemes:\n    basic:\n      type: http\n      scheme: basic\n  schemas:\n    DataFormatter:\n      type: object\n      required:\n        - id\n        - name\n      properties:\n        id:\n          type: string\n          description: Internal formatter ID used in requests to this API\n          example: \"1\"\n        name:\n          type: string\n          description: Name displayed inside RDM app\n          example: \"My .net models decoder\"\n        read-only:\n          type: boolean\n          description: Read-only formatters only receive decode requests\n          \n    DataFormatters:\n      type: array\n      items:\n        $ref: \"#/components/schemas/DataFormatter\"\n        \n    DecodePayload:\n      type: object\n      properties:\n        data: \n          type: string\n          description: Base64 encoded string\n        redis-key-name:\n          type: string\n        redis-key-type:\n          type: string\n          \n    EncodePayload:\n      type: object\n      properties:\n        data:\n          type: string\n          description: Base64 encoded string\n        metadata:\n          type: object\n          description: Metadata from formatter custom ui forms\n\nsecurity:\n  - basic: []\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: \"RESP.app\"\nsite_description: \"RESP.app Documentation (formerly RedisDesktopManager)\"\nsite_author: \"Igor Malinovskiy\"\nsite_favicon: \"favicon.ico\"\nrepo_url: https://github.com/uglide/RedisDesktopManager\nedit_uri: edit/2022/docs/\ntheme: readthedocs\nextra_css:\n- css/extra.css\nnav:\n- Home: 'index.md'\n- Install: 'install.md'\n- Quick Start: 'quick-start.md'\n- Bulk operations: 'bulk-operations.md'\n- Working with large keyspaces: 'lg-keyspaces.md'\n- Native Formatters: 'native-formatters.md'\n- Extension server: 'extension-server.md'\n- FAQ: 'faq.md'\n- Known Issues: 'known-issues.md'\n- AppStore Limitations: 'app-store.md'\n- Development Guide: 'development.md'\nmarkdown_extensions:\n  - markdown.extensions.admonition\nplugins:\n  - render_swagger\n"
  },
  {
    "path": "sonar-project.properties",
    "content": "sonar.projectName=RedisDesktopManager\nsonar.projectKey=uglide_RedisDesktopManager\nsonar.organization=uglide\nsonar.projectVersion=2021.10\n\n# SQ standard properties\nsonar.sources=src\nsonar.sourceEncoding=UTF-8"
  },
  {
    "path": "src/app/app.cpp",
    "content": "#include \"app.h\"\n\n#include <qpython.h>\n#include <pythonlib_loader.h>\n#include <qredisclient/redisclient.h>\n#include <QMessageBox>\n#include <QNetworkProxyFactory>\n#include <QQmlContext>\n#include <QQuickWindow>\n#include <QSettings>\n#include <QSysInfo>\n#include <QUrl>\n#include <QtQml>\n#include <QSslSocket>\n#include <QtConcurrent>\n\n#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX)\n#include \"darkmode.h\"\n#include <QStyleFactory>\n#endif\n\n#include \"common/tabviewmodel.h\"\n#include \"events.h\"\n#include \"models/configmanager.h\"\n#include \"models/connectionconf.h\"\n#include \"models/connectionsmanager.h\"\n#include \"models/key-models/keyfactory.h\"\n#include \"modules/bulk-operations/bulkoperationsmanager.h\"\n#include \"modules/common/sortfilterproxymodel.h\"\n#include \"modules/console/autocompletemodel.h\"\n#include \"modules/console/consolemodel.h\"\n#include \"modules/server-actions/serverstatsmodel.h\"\n#include \"modules/value-editor/embeddedformattersmanager.h\"\n#ifdef ENABLE_EXTERNAL_FORMATTERS\n#include \"modules/extension-server/dataformattermanager.h\"\n#endif\n#include \"modules/value-editor/syntaxhighlighter.h\"\n#include \"modules/value-editor/textcharformat.h\"\n#include \"modules/value-editor/tabsmodel.h\"\n#include \"modules/value-editor/valueviewmodel.h\"\n#include \"qmlutils.h\"\n\n#ifdef Q_OS_WINDOWS\n#include <dwmapi.h>\n#endif\n\nApplication::Application(int& argc, char** argv)\n    : QApplication(argc, argv),\n      m_engine(this),\n      m_qmlUtils(QSharedPointer<QmlUtils>(new QmlUtils())),\n      m_events(QSharedPointer<Events>(new Events()))\n{\n  // Init components required for models and qml\n  initAppInfo();\n  initProxySettings();\n  processCmdArgs();\n  initAppFonts();\n\n#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX)\n  if (isDarkThemeEnabled()) {\n    setStyle(QStyleFactory::create(\"Fusion\"));\n    setPalette(createDarkModePalette());\n  }\n#endif\n\n  initRedisClient();\n  installTranslator();  \n}\n\nvoid Application::initModels() {\n  ConfigManager confManager(m_settingsDir);\n\n  QString config = confManager.getApplicationConfigPath(\"connections.json\");\n\n  if (config.isNull()) {\n    QMessageBox::critical(\n        nullptr,\n        QCoreApplication::translate(\"RESP\",\n                                    \"Settings directory is not writable\"),\n        QCoreApplication::translate(\n            \"RESP\",\n            \"RESP.app can't save connections file to settings directory. \"\n            \"Please change file permissions or restart RESP.app as \"\n            \"administrator.\"));\n\n    throw std::runtime_error(\"invalid connections config\");\n  }\n\n  m_keyFactory = QSharedPointer<KeyFactory>(new KeyFactory());\n\n  m_keyValues =\n      QSharedPointer<ValueEditor::TabsModel>(new ValueEditor::TabsModel(\n          m_keyFactory.staticCast<ValueEditor::AbstractKeyFactory>(), m_events));\n\n  connect(m_events.data(), &Events::openValueTab, m_keyValues.data(),\n          &ValueEditor::TabsModel::openTab);\n  connect(m_events.data(), &Events::newKeyDialog, m_keyFactory.data(),\n          &KeyFactory::createNewKeyRequest);\n  connect(m_events.data(), &Events::closeDbKeys, m_keyValues.data(),\n          &ValueEditor::TabsModel::closeDbKeys);\n\n  m_connections = QSharedPointer<ConnectionsManager>(\n      new ConnectionsManager(config, m_events));\n\n  m_bulkOperations = QSharedPointer<BulkOperations::Manager>(\n      new BulkOperations::Manager(m_connections));\n\n  connect(m_events.data(), &Events::requestBulkOperation,\n          m_bulkOperations.data(),\n          &BulkOperations::Manager::requestBulkOperation);\n\n  m_consoleModel = QSharedPointer<TabViewModel>(\n      new TabViewModel(getTabModelFactory<Console::Model>()));\n\n  connect(m_events.data(), &Events::openConsole, m_consoleModel.data(),\n          &TabViewModel::openTab);\n\n  auto srvStatsFactory = [this](QSharedPointer<RedisClient::Connection> c,\n                                int dbIndex, QList<QByteArray> initCmd) {\n    auto model = QSharedPointer<TabModel>(\n        new ServerStats::Model(c, dbIndex, initCmd), &QObject::deleteLater);\n\n    QObject::connect(model.staticCast<ServerStats::Model>().data(),\n                     &ServerStats::Model::openConsoleTerminal, m_events.data(),\n                     &Events::openConsole);\n\n    return model;\n  };\n\n  m_serverStatsModel = QSharedPointer<TabViewModel>(\n      new TabViewModel(srvStatsFactory));\n\n  connect(m_events.data(), &Events::openServerStats, this,\n          [this](QSharedPointer<RedisClient::Connection> c) {\n            m_serverStatsModel->openTab(c, 0, false);\n          });\n\n#ifdef ENABLE_EXTERNAL_FORMATTERS\n  m_extServerManager =\n      QSharedPointer<RespExtServer::DataFormattersManager>(new RespExtServer::DataFormattersManager(m_engine));\n\n  connect(m_extServerManager.data(), &RespExtServer::DataFormattersManager::error, this,\n          [this](const QString& msg) {\n            qDebug() << \"External formatters:\" << msg;\n            m_events->log(QString(\"External: %1\").arg(msg));\n          });\n\n  connect(m_extServerManager.data(), &RespExtServer::DataFormattersManager::loaded, this,\n          [this]() {\n            qDebug() << \"External formatters loaded\";\n            emit m_events->externalFormattersLoaded();\n          });\n\n  if (!m_extServerUrl.isEmpty()) {\n    m_extServerManager->setUrl(m_extServerUrl);\n  }\n\n  connect(m_events.data(), &Events::appRendered, this, [this]() {\n      if (m_extServerManager) m_extServerManager->loadFormatters();\n  });\n#endif\n\n  m_embeddedFormatters = QSharedPointer<ValueEditor::EmbeddedFormattersManager>(\n      new ValueEditor::EmbeddedFormattersManager());\n\n  connect(m_embeddedFormatters.data(),\n          &ValueEditor::EmbeddedFormattersManager::error, this,\n          [this](const QString& msg) {\n            m_events->log(QString(\"Formatters: %1\").arg(msg));\n          });\n\n  m_consoleAutocompleteModel = QSharedPointer<Console::AutocompleteModel>(\n      new Console::AutocompleteModel());\n\n  connect(m_events.data(), &Events::appRendered, this, [this]() {\n    if (m_connections) m_connections->loadConnections();\n\n    initPython();\n\n    if (m_embeddedFormatters) m_embeddedFormatters->init(m_python);\n    if (m_bulkOperations) m_bulkOperations->setPython(m_python);\n\n    if (m_events) emit m_events->pythonLoaded();\n  });\n}\n\nvoid Application::initAppInfo() {\n  setApplicationName(\"RESP.app - Developer GUI for Redis\");\n  setApplicationVersion(QString(APP_VERSION));\n  setOrganizationDomain(\"redisdesktop.com\");\n  setOrganizationName(\"redisdesktop\");\n\n#ifdef Q_OS_MAC\n  setWindowIcon(QIcon(\":/images/logo.icns\"));\n#else\n  setWindowIcon(QIcon(\":/images/logo.png\"));\n#endif\n\n  qDebug() << \"TLS support:\" << QSslSocket::sslLibraryVersionString();\n}\n\nvoid Application::initAppFonts() {\n  QSettings settings;\n\n  const int minFontSize = 4;\n#ifdef Q_OS_MAC\n  QString defaultFontName(\"Helvetica Neue\");\n  QString defaultMonospacedFont(\"Monaco\");\n  int defaultFontSize = 12;\n#elif defined(Q_OS_WINDOWS)\n  QString defaultFontName(\"Segoe UI\");\n  QString defaultMonospacedFont(\"Consolas\");\n  int defaultFontSize = 11;\n#else\n  QString defaultFontName(\"Open Sans\");\n  QString defaultMonospacedFont(\"Ubuntu Mono\");\n  int defaultFontSize = 11;  \n#endif\n\n  int defaultValueSizeLimit = 150000;\n\n  QString appFont = settings.value(\"app/appFont\", defaultFontName).toString();\n\n  if (appFont.isEmpty())\n    appFont = defaultFontName;\n\n  int appFontSize = settings.value(\"app/appFontSize\", defaultFontSize).toInt();\n\n  if (appFontSize < minFontSize)\n    appFontSize = defaultFontSize;\n\n  if (appFont == \"Open Sans\") {\n#if defined(Q_OS_LINUX)\n    int result = QFontDatabase::addApplicationFont(\"://fonts/OpenSans.ttc\");\n\n    if (result == -1) {\n      appFont = \"Ubuntu\";\n    }\n#elif defined (Q_OS_WINDOWS)\n    appFont = defaultFontName;\n#endif\n  }\n\n  QString valuesFont = settings.value(\"app/valueEditorFont\", defaultMonospacedFont).toString();\n\n  if (valuesFont.isEmpty())\n    valuesFont = defaultMonospacedFont;\n\n  int valuesFontSize = settings.value(\"app/valueEditorFontSize\", defaultFontSize).toInt();\n\n  if (valuesFontSize < minFontSize)\n    valuesFontSize = defaultFontSize;\n\n  int valueSizeLimit = settings.value(\"app/valueSizeLimit\", defaultValueSizeLimit).toInt();\n\n  if (valueSizeLimit < 1000)\n    valueSizeLimit = defaultValueSizeLimit;\n\n  settings.setValue(\"app/appFont\", appFont);\n  settings.setValue(\"app/appFontSize\", appFontSize);\n  settings.setValue(\"app/valueEditorFont\", valuesFont);\n  settings.setValue(\"app/valueEditorFontSize\", valuesFontSize);\n  settings.setValue(\"app/valueSizeLimit\", valueSizeLimit);\n\n  qDebug() << \"App font:\" << appFont << appFontSize;\n  qDebug() << \"Values font:\" << valuesFont;\n  QFont defaultFont(appFont, appFontSize);\n  QApplication::setFont(defaultFont);\n}\n\nvoid Application::initProxySettings() {\n  QSettings settings;\n  QNetworkProxyFactory::setUseSystemConfiguration(\n      settings.value(\"app/useSystemProxy\", false).toBool());\n}\n\nvoid Application::registerQmlTypes() {\n  qmlRegisterType<SortFilterProxyModel>(\"rdm.models\", 1, 0,\n                                        \"SortFilterProxyModel\");\n  qmlRegisterType<SyntaxHighlighter>(\"rdm.models\", 1, 0, \"SyntaxHighlighter\");\n  qmlRegisterType<TextCharFormat>(\"rdm.models\", 1, 0, \"TextCharFormat\");\n  qRegisterMetaType<ServerConfig>();\n}\n\nvoid Application::registerQmlRootObjects() {\n  m_engine.rootContext()->setContextProperty(\"appEvents\", m_events.data());\n  m_engine.rootContext()->setContextProperty(\"qmlUtils\", m_qmlUtils.data());\n  m_engine.rootContext()->setContextProperty(\"connectionsManager\",\n                                             m_connections.data());\n  m_engine.rootContext()->setContextProperty(\"keyFactory\", m_keyFactory.data());\n  m_engine.rootContext()->setContextProperty(\"valuesModel\", m_keyValues.data());\n#ifdef ENABLE_EXTERNAL_FORMATTERS\n  m_engine.rootContext()->setContextProperty(\"formattersManager\",\n                                             m_extServerManager.data());\n#endif\n  m_engine.rootContext()->setContextProperty(\"embeddedFormattersManager\",\n                                             m_embeddedFormatters.data());\n  m_engine.rootContext()->setContextProperty(\"consoleModel\",\n                                             m_consoleModel.data());\n  m_engine.rootContext()->setContextProperty(\"serverStatsModel\",\n                                             m_serverStatsModel.data());\n  m_engine.rootContext()->setContextProperty(\"bulkOperations\",\n                                             m_bulkOperations.data());\n  m_engine.rootContext()->setContextProperty(\"consoleAutocompleteModel\",\n                                             m_consoleAutocompleteModel.data());\n}\n\nvoid Application::initQml() {\n  if (m_renderingBackend == \"auto\") {\n    QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);\n  } else {\n    QQuickWindow::setSceneGraphBackend(m_renderingBackend);\n  }\n\n  registerQmlTypes();\n  registerQmlRootObjects();\n\n  try {\n    m_engine.load(QUrl(QStringLiteral(\"qrc:///app.qml\")));\n  } catch (...) {\n    qDebug() << \"Failed to load app window. Retrying with software renderer...\";\n    QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);\n    m_engine.load(QUrl(QStringLiteral(\"qrc:///app.qml\")));\n  }\n\n  updatePalette();\n  connect(this, &QGuiApplication::paletteChanged, this, &Application::updatePalette);\n\n  qDebug() << \"Rendering backend:\" << QQuickWindow::sceneGraphBackend();\n\n  emit m_events->appRendered();\n}\n\nvoid Application::initPython() {\n  m_python = QSharedPointer<QPython>(new QPython(this, 1, 5));\n  m_python->addImportPath(\"qrc:/python/\");\n\n#ifdef Q_OS_MACOS\n  m_python->addImportPath(applicationDirPath() + \"/../Resources/py\");\n#else\n  m_python->addImportPath(applicationDirPath());\n#endif\n}\n\nvoid Application::installTranslator() {\n  QSettings settings;\n  QString preferredLocale = settings.value(\"app/locale\", \"system\").toString();\n\n  QString locale;\n\n  if (preferredLocale == \"system\") {\n    settings.setValue(\"app/locale\", \"system\");\n    locale = QLocale::system().uiLanguages().first().replace(\"-\", \"_\");\n\n    qDebug() << QLocale::system().uiLanguages();\n\n    if (locale.isEmpty() || locale == \"C\") locale = \"en_US\";\n\n    qDebug() << \"Detected locale:\" << locale;\n  } else {\n    locale = preferredLocale;\n  }\n\n  m_translator = QSharedPointer<QTranslator>(new QTranslator((QObject*)this));\n  if (m_translator->load(QString(\":/translations/rdm_\") + locale)) {\n    qDebug() << \"Load translations file for locale:\" << locale;\n    QCoreApplication::installTranslator(m_translator.data());\n  } else {\n    m_translator.clear();\n  }\n}\n\nvoid Application::processCmdArgs() {\n  QCommandLineParser parser;\n  QCommandLineOption settingsDir(\"settings-dir\",\n                                 \"(Optional) Directory where RESP.app looks/saves \"\n                                 \".rdm directory with connections.json file\",\n                                 \"settingsDir\", QDir::homePath());\n  QCommandLineOption extensionServerUrl(\n      \"extension-server-url\",\n      \"(Optional) Overrides extension server url\",\n      \"extensionServerUrl\",\n      QString());\n\n  QCommandLineOption renderingBackend(\n      \"rendering-backend\",\n      \"(Optional) QML rendering backend [software|opengl|d3d12|'']\",\n      \"renderingBackend\", \"auto\");\n  parser.addHelpOption();\n  parser.addVersionOption();\n  parser.addOption(settingsDir);\n  parser.addOption(extensionServerUrl);\n  parser.addOption(renderingBackend);\n  parser.process(*this);\n\n  m_settingsDir = parser.value(settingsDir);\n  m_extServerUrl = parser.value(extensionServerUrl);\n  m_renderingBackend = parser.value(renderingBackend);\n}\n\nvoid Application::updatePalette()\n{\n    if (m_engine.rootObjects().size() == 0) {\n        qWarning() << \"Cannot update palette. Root object is not loaded.\";\n        return;\n    }\n\n    auto rootObject = m_engine.rootObjects().at(0);\n\n    rootObject->setProperty(\"palette\", QGuiApplication::palette());\n\n#ifdef Q_OS_WINDOWS\n    if (!isDarkThemeEnabled()) return;\n\n    auto window = qobject_cast<QWindow*>(rootObject);\n\n    if (window) {\n      auto winHwnd = reinterpret_cast<HWND>(window->winId());\n      BOOL USE_DARK_MODE = true;\n      BOOL SET_IMMERSIVE_DARK_MODE_SUCCESS = SUCCEEDED(DwmSetWindowAttribute(\n          winHwnd, 20, &USE_DARK_MODE, sizeof(USE_DARK_MODE)));\n\n      if (SET_IMMERSIVE_DARK_MODE_SUCCESS) {\n        // Dirty hack to re-draw window and apply darkmode color\n        rootObject->setProperty(\"visible\", false);\n        rootObject->setProperty(\"visible\", true);\n      }\n    }\n#endif\n}\n"
  },
  {
    "path": "src/app/app.h",
    "content": "#pragma once\n#include <QApplication>\n#include <QFontDatabase>\n#include <QMenu>\n#include <QQmlApplicationEngine>\n#include <QSharedPointer>\n\n#ifndef APP_VERSION\n#include \"../version.h\"\n#endif\n\nclass QmlUtils;\nclass Events;\nclass ConnectionsManager;\nclass Updater;\nclass KeyFactory;\nclass TabViewModel;\nclass QPython;\nnamespace ValueEditor {\nclass TabsModel;\n}\n#ifdef ENABLE_EXTERNAL_FORMATTERS\nnamespace RespExtServer {\nclass DataFormattersManager;\n}\n#endif\nnamespace ValueEditor {\nclass EmbeddedFormattersManager;\n}  // namespace ValueEditor\nnamespace BulkOperations {\nclass Manager;\n}\nnamespace Console {\nclass AutocompleteModel;\n}\n\nclass Application : public QApplication {\n  Q_OBJECT\n\n public:\n  Application(int &argc, char **argv);\n\n  void initModels();\n  void initQml();\n\n private:\n  void initAppInfo();\n  void initAppFonts();\n  void initProxySettings();\n  void initPython();\n\n  void registerQmlTypes();\n  void registerQmlRootObjects();\n  void installTranslator();\n  void processCmdArgs();\n\n private slots:\n  void updatePalette();\n\n private:\n  QQmlApplicationEngine m_engine;\n  QSharedPointer<QmlUtils> m_qmlUtils;\n  QSharedPointer<Events> m_events;\n  QSharedPointer<ConnectionsManager> m_connections;\n  QSharedPointer<KeyFactory> m_keyFactory;\n  QSharedPointer<ValueEditor::TabsModel> m_keyValues;\n#ifdef ENABLE_EXTERNAL_FORMATTERS\n  QSharedPointer<RespExtServer::DataFormattersManager> m_extServerManager;\n#endif\n  QSharedPointer<ValueEditor::EmbeddedFormattersManager> m_embeddedFormatters;\n  QSharedPointer<BulkOperations::Manager> m_bulkOperations;\n  QSharedPointer<TabViewModel> m_consoleModel;\n  QSharedPointer<TabViewModel> m_serverStatsModel;\n  QSharedPointer<Console::AutocompleteModel> m_consoleAutocompleteModel;\n  QSharedPointer<QPython> m_python;\n  QString m_settingsDir;\n  QString m_extServerUrl;\n  QString m_renderingBackend;\n  QSharedPointer<QTranslator> m_translator = nullptr;\n};\n"
  },
  {
    "path": "src/app/apputils.h",
    "content": "#pragma once\n#include <QString>\n#include <QLocale>\n\ninline QString humanReadableSize(qint64 size) {\n  return QLocale().formattedDataSize(size, 2, QLocale::DataSizeSIFormat);\n}\n"
  },
  {
    "path": "src/app/darkmode.h",
    "content": "#pragma once\n#include <QPalette>\n#include <QSettings>\n\nbool isDarkThemeEnabled() {\n#if defined(Q_OS_WINDOWS)\n  QSettings settings;\n  QSettings systemSettings(\n      \"HKEY_CURRENT_\"\n      \"USER\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Themes\\\\Personalize\",\n      QSettings::NativeFormat);\n\n  QString darkMode = settings.value(\"app/darkMode\", \"Auto\").toString();\n  if (darkMode == \"Auto\") {\n      return systemSettings.value(\"AppsUseLightTheme\") == 0;\n  } else if (darkMode == \"On\") {\n      return true;\n  } else {\n      return false;\n  }\n#elif defined(Q_OS_LINUX)\n  QSettings settings;\n\n  return settings.value(\"app/darkModeOn\", false).toBool();\n#else\n  return false;\n#endif\n}\n\nQPalette createDarkModePalette() {\n\n  QColor base = QColor(30, 30, 30);\n  QColor alt = QColor(50, 50, 50);\n  QColor text = QColor(223, 223, 223);\n  QColor buttonText = QColor(170, 170, 170);\n  QColor disabledColor = QColor(127, 127, 127);\n\n  QPalette p(alt, base);\n\n  p.setColor(QPalette::Light, QColor(76,76,76));\n  p.setColor(QPalette::Dark, QColor(235,235,235));\n  p.setColor(QPalette::Midlight, QColor(76,76,76));\n  p.setColor(QPalette::Mid, QColor(66,66,66));\n  p.setColor(QPalette::Window, base);\n  p.setColor(QPalette::WindowText, text);\n  p.setColor(QPalette::Base, base);\n  p.setColor(QPalette::AlternateBase, alt);\n  p.setColor(QPalette::ToolTipBase, alt);\n  p.setColor(QPalette::ToolTipText, Qt::white);\n  p.setColor(QPalette::Text, text);\n  p.setColor(QPalette::Disabled, QPalette::Text, disabledColor);\n  p.setColor(QPalette::Button, alt);\n  p.setColor(QPalette::ButtonText, buttonText);\n  p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor);\n  p.setColor(QPalette::BrightText, text.lighter(80));\n  p.setColor(QPalette::Link, QColor(42, 130, 218));\n  p.setColor(QPalette::Highlight, QColor(42, 130, 218));\n  p.setColor(QPalette::HighlightedText, Qt::black);\n  p.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor);\n\n  p.setBrush(QPalette::Active, QPalette::Highlight,  QColor(42, 130, 218));\n  p.setBrush(QPalette::Inactive, QPalette::Highlight, QColor(42, 130, 218));\n\n  return p;\n}\n"
  },
  {
    "path": "src/app/events.cpp",
    "content": "#include \"events.h\"\n\nvoid Events::registerLoggerForConnection(RedisClient::Connection& c) {\n  auto self = sharedFromThis().toWeakRef();\n  QObject::connect(\n      &c, &RedisClient::Connection::log, this, [self](const QString& info) {\n        if (!self) return;\n        emit self.toStrongRef()->log(QString(\"Connection: %1\").arg(info));\n      }, Qt::QueuedConnection);\n\n  QObject::connect(\n      &c, &RedisClient::Connection::error, this, [self](const QString& error) {\n        if (!self) return;\n        emit self.toStrongRef()->log(QString(\"Connection: %1\").arg(error));\n      }, Qt::QueuedConnection);\n}\n"
  },
  {
    "path": "src/app/events.h",
    "content": "#pragma once\n#include <qredisclient/connection.h>\n#include <QEnableSharedFromThis>\n#include <QObject>\n#include <QRegExp>\n#include <QSharedPointer>\n#include <QString>\n#include <functional>\n#include \"modules/bulk-operations/bulkoperationsmanager.h\"\n#include \"common/callbackwithowner.h\"\n\nnamespace ConnectionsTree {\nclass KeyItem;\nclass TreeItem;\n}\n\nclass Events : public QObject, public QEnableSharedFromThis<Events> {\n  Q_OBJECT\n\n public:\n  void registerLoggerForConnection(RedisClient::Connection& c);\n\n signals:\n\n  // Tabs\n  void openValueTab(QSharedPointer<RedisClient::Connection> connection,\n                    QSharedPointer<ConnectionsTree::KeyItem> key,\n                    bool inNewTab);\n\n  void openConsole(QSharedPointer<RedisClient::Connection> connection,\n                   int dbIndex, bool inNewTab,\n                   QList<QByteArray> initCmd = QList<QByteArray>());\n\n  void openServerStats(QSharedPointer<RedisClient::Connection> connection);\n\n  void closeDbKeys(QSharedPointer<RedisClient::Connection> connection,\n                   int dbIndex,\n                   const QRegExp& filter = QRegExp(\"*\", Qt::CaseSensitive,\n                                                   QRegExp::Wildcard));\n\n  // Dialogs\n  void requestBulkOperation(\n      QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n      BulkOperations::Manager::Operation op, QRegExp keyPattern,\n      BulkOperations::AbstractOperation::OperationCallback callback);\n\n  void newKeyDialog(\n      QSharedPointer<RedisClient::Connection> connection,\n      QSharedPointer<CallbackWithOwner<ConnectionsTree::TreeItem>> callback,\n      int dbIndex, QString keyPrefix);\n\n  // Notifications\n  void error(const QString& msg);\n\n  void log(const QString& msg);\n\n  void appRendered();\n\n  void pythonLoaded();\n\n  void externalFormattersLoaded();\n\n};\n"
  },
  {
    "path": "src/app/jsonutils.cpp",
    "content": "#include \"jsonutils.h\"\n\n#include <QDebug>\n#include <simdjson.h>\n\n// Based on https://github.com/nlohmann/json/blob/ec7a1d834773f9fee90d8ae908a0c9933c5646fc/src/json.hpp#L4604-L4697\n// Copyright &copy; 2013-2015 Niels Lohmann.\n// The code is licensed under the MIT License\nstd::size_t extra_space(const std::string_view& s) noexcept\n{\n    std::size_t result = 0;\n\n    for (const auto& c : s)\n    {\n        switch (c)\n        {\n            case '\"':\n            case '\\\\':\n            case '\\b':\n            case '\\f':\n            case '\\n':\n            case '\\r':\n            case '\\t':\n            {\n                // from c (1 byte) to \\x (2 bytes)\n                result += 1;\n                break;\n            }\n\n            default:\n            {\n                if (c >= 0x00 and c <= 0x1f)\n                {\n                    // from c (1 byte) to \\uxxxx (6 bytes)\n                    result += 5;\n                }\n                break;\n            }\n        }\n    }\n\n    return result;\n}\n\nstd::string escape_string(const std::string_view& s) noexcept\n{\n    const auto space = extra_space(s);\n    if (space == 0)\n    {\n        return std::string(s);\n    }\n\n    // create a result string of necessary size\n    std::string result(s.size() + space, '\\\\');\n    std::size_t pos = 0;\n\n    for (const auto& c : s)\n    {\n        switch (c)\n        {\n            // quotation mark (0x22)\n            case '\"':\n            {\n                result[pos + 1] = '\"';\n                pos += 2;\n                break;\n            }\n\n            // reverse solidus (0x5c)\n            case '\\\\':\n            {\n                // nothing to change\n                pos += 2;\n                break;\n            }\n\n            // backspace (0x08)\n            case '\\b':\n            {\n                result[pos + 1] = 'b';\n                pos += 2;\n                break;\n            }\n\n            // formfeed (0x0c)\n            case '\\f':\n            {\n                result[pos + 1] = 'f';\n                pos += 2;\n                break;\n            }\n\n            // newline (0x0a)\n            case '\\n':\n            {\n                result[pos + 1] = 'n';\n                pos += 2;\n                break;\n            }\n\n            // carriage return (0x0d)\n            case '\\r':\n            {\n                result[pos + 1] = 'r';\n                pos += 2;\n                break;\n            }\n\n            // horizontal tab (0x09)\n            case '\\t':\n            {\n                result[pos + 1] = 't';\n                pos += 2;\n                break;\n            }\n\n            default:\n            {\n                if (c >= 0x00 and c <= 0x1f)\n                {\n                    // print character c as \\uxxxx\n                    std::snprintf(&result[pos + 1], 7, \"u%04x\", int(c));\n                    pos += 6;\n                    // overwrite trailing null character\n                    result[pos] = '\\\\';\n                }\n                else\n                {\n                    // all other characters are added as-is\n                    result[pos++] = c;\n                }\n                break;\n            }\n        }\n    }\n\n    return result;\n}\n\nQByteArray escapeJsonKey(std::string_view key) {\n    return QByteArray::fromStdString(escape_string(key));\n}\n\nvoid print_json(QByteArray &result,\n                simdjson::ondemand::value element,\n                long level, bool objectValue = false) {\n  using namespace simdjson::ondemand;\n\n  QByteArray whitespace = QByteArray().fill(' ', level * 2);\n  bool add_comma;\n\n  if (!objectValue) result.append(whitespace);\n\n  switch (element.type()) {\n    case json_type::array:\n      result.append(\"[\\n\");\n      add_comma = false;\n      for (auto child : element.get_array()) {\n        if (add_comma) {\n          result.append(\",\\n\");\n        }\n        print_json(result, child.value(), level + 1);\n        add_comma = true;\n      }\n\n      result.append('\\n');\n      result.append(whitespace);\n      result.append(\"]\");\n      break;\n    case json_type::object:\n      result.append(\"{\\n\");\n      add_comma = false;\n      for (auto field : element.get_object()) {\n        if (add_comma) {\n          result.append(\",\\n\");\n        }\n\n        result.append(whitespace);\n        result.append(\"  \");\n        result.append(QString(\"\\\"%1\\\": \")\n                          .arg(QString::fromUtf8(escapeJsonKey(field.unescaped_key())))\n                          .toUtf8());\n        print_json(result, field.value(), level + 1, true);\n        add_comma = true;\n      }\n      result.append('\\n');\n      result.append(whitespace);\n      result.append(\"}\");\n      break;\n    case json_type::number:\n      result.append(QByteArray::fromStdString(\n                        std::string(std::string_view(element.raw_json_token())))\n                        .trimmed());\n      break;\n    case json_type::string:\n      result.append(QByteArray::fromStdString(\n                        std::string(std::string_view(element.raw_json_token())))\n                        .trimmed());\n      break;\n    case json_type::boolean:\n      result.append(bool(element) ? \"true\" : \"false\");\n      break;\n    case json_type::null:\n      result.append(\"null\");\n      break;\n  }\n}\n\nQByteArray JSONUtils::prettyPrintJSON(QByteArray val)\n{\n    QByteArray result;\n    result.reserve(val.size() * 32);\n\n    val.resize(val.size() + simdjson::SIMDJSON_PADDING);\n\n    simdjson::ondemand::parser p;\n\n    try {\n      auto doc = p.iterate(val.data(), val.size());\n\n      if (doc.is_scalar()) {\n          return val;\n      }\n\n      print_json(result, simdjson::ondemand::value(doc), 0);\n    } catch (const std::exception& e) {\n      qDebug() << \"Cannot parse JSON:\" << e.what();\n      return QByteArray();\n    }\n\n    return result;\n}\n\nQByteArray JSONUtils::minifyJSON(const QByteArray &val)\n{\n    QByteArray minified;\n    minified.resize(val.size());\n\n    size_t new_length{};\n    auto error = simdjson::minify(val.data(), val.size(), minified.data(), new_length);\n\n    if (error != 0) {\n        qDebug() << \"Failed to minify JSON with simdjson:\" << error;\n        return QByteArray();\n    }\n\n    minified.resize(new_length);\n\n    return minified;\n}\n\nbool JSONUtils::isJSON(QByteArray val)\n{\n    int originalSize = val.size();\n    val.resize(val.size() + simdjson::SIMDJSON_PADDING);\n\n    simdjson::dom::parser parser;\n    simdjson::dom::element data;\n    auto error = parser.parse(val.data(), originalSize, false).get(data);\n\n    // NOTE(u_glide): Workaround to distinguish invalid JSON and valid JSON with Big Int\n    if (error == simdjson::NUMBER_ERROR) {\n        simdjson::ondemand::parser p;\n\n        try {\n          auto doc = p.iterate(val.data(), val.size());\n          return !doc.is_scalar();\n        } catch (const std::exception& e) {\n          qDebug() << \"JSON is not valid:\" << e.what();\n          return false;\n        }\n    } else if (error != simdjson::SUCCESS) {\n        qDebug() << \"JSON is not valid:\" << simdjson::error_message(error);\n        return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/app/jsonutils.h",
    "content": "#pragma once\n#include <QByteArray>\n\nnamespace JSONUtils {\n\nQByteArray prettyPrintJSON(QByteArray val);\n\nbool isJSON(QByteArray val);\n\nQByteArray minifyJSON(const QByteArray& val);\n\n};  // namespace JSONUtils\n"
  },
  {
    "path": "src/app/models/configmanager.cpp",
    "content": "#include \"configmanager.h\"\r\n#include <qredisclient/utils/compat.h>\r\n#include <QDebug>\r\n#include <QDir>\r\n#include <QFile>\r\n#include <QFileInfo>\r\n#include <QJsonArray>\r\n#include <QJsonDocument>\r\n#include <QJsonObject>\r\n#include <QVariantHash>\r\n#include <QXmlStreamReader>\r\n#include \"app/models/connectionconf.h\"\r\n\r\nConfigManager::ConfigManager(const QString &basePath) : m_basePath(basePath) {}\r\n\r\nQString ConfigManager::getApplicationConfigPath(const QString &configFile,\r\n                                                bool checkPath) {\r\n  QString configDir = getConfigPath(m_basePath);\r\n  QDir settingsPath(configDir);\r\n\r\n  if (!settingsPath.exists() && settingsPath.mkpath(configDir)) {\r\n    qDebug() << \"Config Dir created\";\r\n  }\r\n\r\n  QString configPath = QString(\"%1/%2\").arg(configDir).arg(configFile);\r\n\r\n  if (checkPath && !chechPath(configPath)) return QString();\r\n\r\n  return configPath;\r\n}\r\n\r\nbool ConfigManager::chechPath(const QString &configPath) {\r\n  QFile testConfig(configPath);\r\n  QFileInfo checkPermissions(configPath);\r\n\r\n  if (!testConfig.exists() && testConfig.open(QIODevice::ReadWrite))\r\n    testConfig.close();\r\n\r\n  if (checkPermissions.isWritable()) {\r\n    setPermissions(testConfig);\r\n    return true;\r\n  }\r\n\r\n  return false;\r\n}\r\n\r\nvoid ConfigManager::setPermissions(QFile &file) {\r\n#ifdef Q_OS_WIN\r\n  extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;\r\n  qt_ntfs_permission_lookup++;\r\n#endif\r\n\r\n  if (!file.setPermissions(QFile::ReadUser | QFile::WriteUser))\r\n    qWarning() << \"Cannot set permissions for config folder\";\r\n\r\n#ifdef Q_OS_WIN\r\n  qt_ntfs_permission_lookup--;\r\n#endif\r\n}\r\n\r\nQString ConfigManager::getConfigPath(QString basePath) {\r\n  QString configDir;\r\n#ifdef Q_OS_MACX\r\n  if (basePath == QDir::homePath()) {\r\n    configDir = \"/Library/Preferences/rdm/\";\r\n  } else {\r\n    configDir = \".rdm\";\r\n  }\r\n\r\n  configDir =\r\n      QDir::toNativeSeparators(QString(\"%1/%2\").arg(basePath).arg(configDir));\r\n#else\r\n  configDir =\r\n      QDir::toNativeSeparators(QString(\"%1/%2\").arg(basePath).arg(\".rdm\"));\r\n#endif\r\n  return configDir;\r\n}\r\n\r\nbool saveJsonArrayToFile(const QJsonArray &c, const QString &f) {\r\n  QJsonDocument config(c);\r\n  QFile confFile(f);\r\n\r\n  if (confFile.open(QIODevice::WriteOnly)) {\r\n    QTextStream outStream(&confFile);\r\n    outStream.setCodec(\"UTF-8\");\r\n    outStream << config.toJson();\r\n    confFile.close();\r\n    return true;\r\n  }\r\n  return false;\r\n}\r\n"
  },
  {
    "path": "src/app/models/configmanager.h",
    "content": "#pragma once\r\n#include <QString>\r\n#include <QFile>\r\n#include <QDir>\r\n#include <QJsonArray>\r\n\r\nclass ConfigManager\r\n{\r\npublic:\r\n    explicit ConfigManager(const QString& basePath = QDir::homePath());\r\n    QString getApplicationConfigPath(const QString &, bool checkPath=true);    \r\npublic:\r\n    static QString getConfigPath(QString basePath = QDir::homePath());    \r\n\r\nprivate:\r\n    static bool chechPath(const QString&);\r\n    static void setPermissions(QFile&);    \r\nprivate:\r\n    QString m_basePath;\r\n};\r\n\r\nbool saveJsonArrayToFile(const QJsonArray& c, const QString& f);\r\n"
  },
  {
    "path": "src/app/models/connectionconf.cpp",
    "content": "#include \"connectionconf.h\"\n\nServerConfig::ServerConfig(const QString &host, const QString &auth, const uint port, const QString &name)\n    : RedisClient::ConnectionConfig(host, auth, port, name),\n      m_owner(QSharedPointer<TreeOperations>())\n{\n}\n\nServerConfig::ServerConfig(const QVariantHash &options)\n    : RedisClient::ConnectionConfig(options),\n      m_owner(QSharedPointer<TreeOperations>())\n{\n\n}\n\nQString ServerConfig::keysPattern() const\n{\n    return param<QString>(\"keys_pattern\", QString(DEFAULT_KEYS_GLOB_PATTERN));\n}\n\nvoid ServerConfig::setKeysPattern(QString keyGlobPattern)\n{\n    setParam<QString>(\"keys_pattern\", keyGlobPattern);\n}\n\nQString ServerConfig::namespaceSeparator() const\n{\n    return param<QString>(\"namespace_separator\", QString(DEFAULT_NAMESPACE_SEPARATOR));\n}\n\nvoid ServerConfig::setNamespaceSeparator(QString ns)\n{\n    return setParam<QString>(\"namespace_separator\", ns);\n}\n\nuint ServerConfig::databaseScanLimit() const\n{\n    return param<uint>(\"db_scan_limit\", DEFAULT_DB_SCAN_LIMIT);\n}\n\nvoid ServerConfig::setDatabaseScanLimit(uint limit)\n{\n    setParam<uint>(\"db_scan_limit\", limit);\n}\n\nbool ServerConfig::useSshTunnel() const\n{\n    return RedisClient::ConnectionConfig::useSshTunnel();\n}\n\nQWeakPointer<TreeOperations> ServerConfig::owner() const\n{\n    return m_owner;\n}\n\nvoid ServerConfig::setOwner(QWeakPointer<TreeOperations> o)\n{\n    m_owner = o;\n}\n\nQVariantMap ServerConfig::filterHistory()\n{\n    return param<QVariantMap>(\"filter_history\");\n}\n\nvoid ServerConfig::setFilterHistory(QVariantMap filterHistory)\n{\n    setParam<QVariantMap>(\"filter_history\", filterHistory);\n}\n\nbool ServerConfig::askForSshPassword() const\n{\n    return param<bool>(\"ask_ssh_password\", false);\n}\n\nvoid ServerConfig::setAskForSshPassword(bool v)\n{\n    setParam<bool>(\"ask_ssh_password\", v);\n}\n\nQString ServerConfig::defaultFormatter() const\n{\n    return param<QString>(\"default_formatter\", QString(\"auto\"));\n}\n\nvoid ServerConfig::setDefaultFormatter(const QString &v)\n{\n    setParam<QString>(\"default_formatter\", v);\n}\n\nQString ServerConfig::iconColor() const\n{\n    return param<QString>(\"icon_color\", QString(\"\"));\n}\n\nvoid ServerConfig::setIconColor(const QString &v)\n{\n    setParam<QString>(\"icon_color\", v);\n}\n"
  },
  {
    "path": "src/app/models/connectionconf.h",
    "content": "#pragma once\n#include <QObject>\n#include <qredisclient/connectionconfig.h>\n\n\nclass TreeOperations;\n\nclass ServerConfig : public RedisClient::ConnectionConfig\n{\n    Q_GADGET\n\n    /* Basic settings */\n    Q_PROPERTY(QString name READ name WRITE setName)\n    Q_PROPERTY(QString host READ host WRITE setHost)\n    Q_PROPERTY(uint port READ port WRITE setPort)\n    Q_PROPERTY(QString auth READ auth WRITE setAuth)\n    Q_PROPERTY(QString username READ username WRITE setUsername)\n\n    /* SSL settings */\n    Q_PROPERTY(bool sslEnabled READ useSsl WRITE setSsl)\n    Q_PROPERTY(QString sslLocalCertPath READ sslLocalCertPath WRITE setSslLocalCertPath)\n    Q_PROPERTY(QString sslPrivateKeyPath READ sslPrivateKeyPath WRITE setSslPrivateKeyPath)\n    Q_PROPERTY(QString sslCaCertPath READ sslCaCertPath WRITE setSslCaCertPath)\n\n    /* SSH Settings */\n    Q_PROPERTY(QString sshPassword READ sshPassword WRITE setSshPassword)\n    Q_PROPERTY(bool askForSshPassword READ askForSshPassword WRITE setAskForSshPassword)\n    Q_PROPERTY(QString sshUser READ sshUser WRITE setSshUser)\n    Q_PROPERTY(QString sshHost READ sshHost WRITE setSshHost)\n    Q_PROPERTY(uint sshPort READ sshPort WRITE setSshPort)\n    Q_PROPERTY(QString sshPrivateKey READ getSshPrivateKeyPath WRITE setSshPrivateKeyPath)\n    Q_PROPERTY(bool sshAgent READ sshAgent WRITE setSshAgent)\n    Q_PROPERTY(QString sshAgentPath READ sshAgentPath WRITE setSshAgentPath)\n\n    /* Advanced settings */\n    Q_PROPERTY(QString keysPattern READ keysPattern WRITE setKeysPattern)\n    Q_PROPERTY(QString namespaceSeparator READ namespaceSeparator WRITE setNamespaceSeparator)\n    Q_PROPERTY(uint executeTimeout READ executeTimeout WRITE setExecutionTimeout)\n    Q_PROPERTY(uint connectionTimeout READ connectionTimeout WRITE setConnectionTimeout)    \n    Q_PROPERTY(bool overrideClusterHost READ overrideClusterHost WRITE setClusterHostOverride)\n    Q_PROPERTY(bool ignoreSSLErrors READ ignoreAllSslErrors WRITE setIgnoreAllSslErrors)\n    Q_PROPERTY(uint databaseScanLimit READ databaseScanLimit WRITE setDatabaseScanLimit)\n    Q_PROPERTY(QString defaultFormatter READ defaultFormatter WRITE setDefaultFormatter)\n    Q_PROPERTY(QString iconColor READ iconColor WRITE setIconColor)\n\n\npublic:\n    static const char DEFAULT_NAMESPACE_SEPARATOR = ':';\n    static const char DEFAULT_KEYS_GLOB_PATTERN = '*';\n    static const bool DEFAULT_LUA_KEYS_LOADING = false;\n    static const uint DEFAULT_DB_SCAN_LIMIT = 20;\n    static constexpr const char* SSH_SECRET_ID = \"ssh_password\";\n\npublic:\n    ServerConfig(const QString & host = \"127.0.0.1\", const QString & auth = \"\",\n                     const uint port = DEFAULT_REDIS_PORT, const QString & name = \"\");\n\n    explicit ServerConfig(const QVariantHash& options);\n\n    QString keysPattern() const;\n    void setKeysPattern(QString keyGlobPattern);\n\n    QString namespaceSeparator() const;\n    void setNamespaceSeparator(QString);\n\n    void setLuaKeysLoading(bool);\n\n    uint databaseScanLimit() const;\n    void setDatabaseScanLimit(uint limit);\n\n    Q_INVOKABLE bool useSshTunnel() const;\n\n    QWeakPointer<TreeOperations> owner() const;\n    void setOwner(QWeakPointer<TreeOperations> o);\n\n    QVariantMap filterHistory();\n    void setFilterHistory(QVariantMap filterHistory);\n\n    bool askForSshPassword() const;\n    void setAskForSshPassword(bool v);\n\n    QString defaultFormatter() const;\n    void setDefaultFormatter(const QString& v);\n\n    QString iconColor() const;\n    void setIconColor(const QString& v);\n\nprivate:\n    QWeakPointer<TreeOperations> m_owner;\n};\n\nQ_DECLARE_METATYPE(ServerConfig)\n"
  },
  {
    "path": "src/app/models/connectiongroup.cpp",
    "content": "#include \"connectiongroup.h\"\n\n#include \"connections-tree/items/servergroup.h\"\n\nConnectionGroup::ConnectionGroup(QSharedPointer<ConnectionsTree::ServerGroup> g)\n    : m_group(g) {}\n\nConnectionGroup::ConnectionGroup() : m_group(nullptr) {}\n\nQString ConnectionGroup::name() const {\n  if (!m_group) return QString();\n\n  return m_group->getDisplayName();\n}\n\nvoid ConnectionGroup::setName(const QString &n) {\n  if (m_group) m_group->setName(n);\n}\n\nQSharedPointer<ConnectionsTree::ServerGroup> ConnectionGroup::serverGroup() const {\n  return m_group;\n}\n"
  },
  {
    "path": "src/app/models/connectiongroup.h",
    "content": "#pragma once\n#include <QObject>\n#include <QSharedPointer>\n\nnamespace ConnectionsTree {\nclass ServerGroup;\n}\n\nclass ConnectionGroup {\n  Q_GADGET\n\n  Q_PROPERTY(QString name READ name WRITE setName)\n\n public:\n  ConnectionGroup();\n\n  ConnectionGroup(QSharedPointer<ConnectionsTree::ServerGroup> g);\n\n  QString name() const;\n\n  void setName(const QString& n);\n\n  QSharedPointer<ConnectionsTree::ServerGroup> serverGroup() const;\n\n private:\n  QSharedPointer<ConnectionsTree::ServerGroup> m_group;\n};\n\nQ_DECLARE_METATYPE(ConnectionGroup)\n"
  },
  {
    "path": "src/app/models/connectionsmanager.cpp",
    "content": "#include \"connectionsmanager.h\"\n\n#include <QAbstractItemModel>\n#include <QDebug>\n#include <QFile>\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QUrlQuery>\n\n#include \"app/events.h\"\n#include \"configmanager.h\"\n#include \"modules/bulk-operations/bulkoperationsmanager.h\"\n#include \"modules/connections-tree/items/serveritem.h\"\n#include \"modules/connections-tree/items/servergroup.h\"\n#include \"modules/value-editor/tabsmodel.h\"\n\nConnectionsManager::ConnectionsManager(const QString& configPath,\n                                       QSharedPointer<Events> events)\n    : ConnectionsTree::Model(), m_configPath(configPath), m_events(events) {\n  connect(this, &ConnectionsTree::Model::error, m_events.data(),\n          &Events::error);\n}\n\nvoid ConnectionsManager::loadConnections() {\n  if (!m_configPath.isEmpty() && QFile::exists(m_configPath)) {\n    loadConnectionsConfigFromFile(m_configPath);\n  }\n\n  emit connectionsLoaded();\n}\n\nvoid ConnectionsManager::addNewConnection(\n    const ServerConfig& config, bool saveToConfig,\n    QSharedPointer<ConnectionsTree::ServerGroup> group) {\n  createServerItemForConnection(config, group);\n\n  if (saveToConfig) saveConfig();\n\n  buildConnectionsCache();\n}\n\nvoid ConnectionsManager::addNewGroup(const QString& name) {\n  auto group = QSharedPointer<ConnectionsTree::ServerGroup>(\n      new ConnectionsTree::ServerGroup(\n          name, *static_cast<ConnectionsTree::Model*>(this)));\n\n  addGroup(group);\n\n  saveConfig();\n}\n\nvoid ConnectionsManager::updateGroup(const ConnectionGroup &group)\n{\n    auto serverGroup = group.serverGroup();\n\n    if (!serverGroup){\n        qWarning() << \"invalid server group\";\n        return;\n    }\n\n    emit itemChanged(serverGroup);\n\n    saveConfig();\n\n    buildConnectionsCache();\n}\n\nvoid ConnectionsManager::updateConnection(const ServerConfig& config) {\n  if (!config.owner()) return addNewConnection(config);\n\n  auto treeOperations = config.owner().toStrongRef();\n\n  if (!treeOperations) return;\n\n  treeOperations->setConfig(config);\n\n  saveConfig();\n}\n\nbool ConnectionsManager::importConnections(const QString& path) {\n  if (loadConnectionsConfigFromFile(path, true)) {\n    emit sizeChanged();\n    return true;\n  }\n  return false;\n}\n\nbool ConnectionsManager::loadConnectionsConfigFromFile(const QString& config,\n                                                       bool saveChangesToFile) {\n  QJsonArray connections;\n\n  QFile conf(config);\n\n  if (!conf.open(QIODevice::ReadOnly)) return false;\n\n  QByteArray data = conf.readAll();\n  conf.close();\n\n  QJsonDocument jsonConfig = QJsonDocument::fromJson(data);\n\n  if (jsonConfig.isEmpty()) return true;\n\n  if (!jsonConfig.isArray()) {\n    return false;\n  }\n\n  connections = jsonConfig.array();\n\n  for (QJsonValue connection : connections) {\n    if (!connection.isObject()) continue;\n\n    auto obj = connection.toObject();\n\n    bool isValidGroup = obj.contains(\"type\") && obj.contains(\"connections\") &&\n            obj.contains(\"name\") && obj[\"connections\"].isArray() &&\n            obj[\"type\"].toString().toLower() == \"group\";\n\n    if (isValidGroup) {\n      auto groupConnections = obj[\"connections\"].toArray();\n\n      auto group = QSharedPointer<ConnectionsTree::ServerGroup>(\n          new ConnectionsTree::ServerGroup(\n              obj[\"name\"].toString(),\n              *static_cast<ConnectionsTree::Model*>(this)));\n\n      for (const QJsonValue &c : qAsConst(groupConnections)) {\n        if (!c.isObject()) continue;\n\n        ServerConfig conf(c.toObject().toVariantHash());\n\n        if (conf.isNull()) continue;\n\n        conf.setId(QUuid::createUuid().toByteArray());\n        addNewConnection(conf, false, group);\n      }\n\n      addGroup(group);      \n    } else {\n        ServerConfig conf(obj.toVariantHash());\n        if (conf.isNull()) continue;\n        addNewConnection(conf, false);\n    }\n  }\n\n  if (saveChangesToFile) saveConfig();\n\n  buildConnectionsCache();\n\n  return true;\n}\n\nvoid ConnectionsManager::tryToConnect(const ServerConfig &config, QJSValue jsCallback)\n{\n    RedisClient::Connection testConnection(config);\n    m_events->registerLoggerForConnection(testConnection);\n\n    try {\n      jsCallback.call(QJSValueList{testConnection.connect()});\n    } catch (const RedisClient::Connection::Exception&) {\n      jsCallback.call(QJSValueList{false});\n    }\n}\n\nvoid ConnectionsManager::saveConfig() {\n  saveConnectionsConfigToFile(m_configPath);\n}\n\nbool ConnectionsManager::saveConnectionsConfigToFile(\n    const QString& pathToFile) {\n  QJsonArray connections;\n\n  auto addConfig = [](QSharedPointer<ConnectionsTree::TreeItem> i,\n                      QJsonArray& connections) {\n    auto srvItem = i.dynamicCast<ConnectionsTree::ServerItem>();\n\n    if (!srvItem) return;\n\n    auto op = srvItem->getOperations().dynamicCast<TreeOperations>();\n\n    if (!op) return;\n\n    auto config = op->config();\n    QSet<QString> ignoreFields {\"id\"};\n\n    if (config.askForSshPassword()) {\n        ignoreFields.insert(ServerConfig::SSH_SECRET_ID);\n    }\n\n    connections.push_back(QJsonValue(config.toJsonObject(ignoreFields)));\n  };\n\n  for (auto item : m_treeItems) {\n    if (item->type() == \"server_group\") {\n      QJsonObject group;\n      group[\"type\"] = \"group\";\n      group[\"name\"] = item->getDisplayName();\n\n      QJsonArray groupConnections;\n\n      for (auto srv : item->getAllChilds()) {\n        addConfig(srv, groupConnections);\n      }\n\n      group[\"connections\"] = groupConnections;\n      connections.push_back(QJsonValue(group));\n\n    } else if (item->type() == \"server\") {\n      addConfig(item, connections);\n    }\n  }\n\n  return saveJsonArrayToFile(connections, pathToFile);\n}\n\nvoid ConnectionsManager::testConnectionSettings(const ServerConfig& config,\n                                                QJSValue jsCallback) {\n  if (!jsCallback.isCallable()) {\n    qDebug() << \"JS callback is not callable\";\n    return;\n  }\n\n  if (config.askForSshPassword()) {\n    m_jsCallback = jsCallback;\n\n    emit askUserForConnectionSecret(config, ServerConfig::SSH_SECRET_ID);\n  } else {\n    tryToConnect(config, jsCallback);\n  }\n}\n\nvoid ConnectionsManager::proceedWithConnectionSecret(const ServerConfig &config)\n{\n    if (m_jsCallback.isCallable()) {\n        tryToConnect(config, m_jsCallback);\n        m_jsCallback = QJSValue();\n        return;\n    }\n\n    if (!config.owner()) {\n        qWarning() << \"Invalid config with secret\";\n        return;\n    }\n\n    auto treeOperations = config.owner().toStrongRef();\n\n    if (!treeOperations) {\n        qWarning() << \"Config with secret doesn't have owner\";\n        return;\n    }\n\n    treeOperations->proceedWithSecret(config);\n}\n\nServerConfig ConnectionsManager::createEmptyConfig() const {\n  return ServerConfig();\n}\n\nServerConfig ConnectionsManager::parseConfigFromRedisConnectionString(const QString& connectionString) const {\n    QUrl url = QUrl(connectionString);\n\n    QUrlQuery query = QUrlQuery(url.query());\n\n    ServerConfig config;\n\n    config.setHost(url.host().isEmpty() || url.host() == \"localhost\" ? \"127.0.0.1\" : url.host());\n\n    config.setPort(url.port() == -1 ? 6379 : url.port());\n\n    config.setUsername(url.userName());\n\n    config.setAuth(url.password().isEmpty() ? query.queryItemValue(\"password\") : url.password());\n\n    if (url.scheme() == \"rediss\" || (!query.isEmpty() && query.queryItemValue(\"ssl\") == \"true\")) {\n        config.setSsl(true);\n    }\n\n    return config;\n}\n\nbool ConnectionsManager::isRedisConnectionStringValid(\n    const QString& connectionString) {\n  QUrl url;\n  if (connectionString.startsWith(\"redis://\") ||\n      connectionString.startsWith(\"rediss://\")) {\n    url = QUrl(connectionString);\n  } else {\n    url = QUrl(QString(\"redis://%1\").arg(connectionString));\n  }\n\n  return url.isValid() &&\n         (url.scheme() == \"redis\" || url.scheme() == \"rediss\") &&\n         !url.host().isEmpty();\n}\n\nint ConnectionsManager::size() {\n  int connectionsCount = 0;\n\n  for (auto item : qAsConst(m_treeItems)) {\n    if (item->type() == \"server_group\") {\n      connectionsCount += item->childCount();\n    } else if (item->type() == \"server\") {\n      connectionsCount++;\n    }\n  }\n  return connectionsCount;\n}\n\nQSharedPointer<RedisClient::Connection> ConnectionsManager::getByIndex(\n    int index) {\n  auto op = m_connectionsCache.values().at(index)->getOperations();\n\n  if (!op) return QSharedPointer<RedisClient::Connection>();\n\n  auto treeOp = op.dynamicCast<TreeOperations>();\n\n  if (!treeOp) return QSharedPointer<RedisClient::Connection>();\n\n  return treeOp->connection();\n}\n\nQStringList ConnectionsManager::getConnections() {\n  return m_connectionsCache.keys();\n}\n\nvoid ConnectionsManager::applyGroupChanges() {\n  ConnectionsTree::Model::applyGroupChanges();\n\n  buildConnectionsCache();\n\n  saveConfig();\n}\n\nvoid ConnectionsManager::createServerItemForConnection(\n    const ServerConfig& config,\n    QSharedPointer<ConnectionsTree::ServerGroup> group) {\n  using namespace ConnectionsTree;\n\n  auto treeModel =\n      QSharedPointer<TreeOperations>(new TreeOperations(config, m_events));\n\n  connect(treeModel.data(), &TreeOperations::createNewConnection, this,\n          [this](const ServerConfig& config) { addNewConnection(config); });\n\n  connect(treeModel.data(), &TreeOperations::secretRequired, this,\n          &ConnectionsManager::askUserForConnectionSecret);\n\n  QWeakPointer<TreeItem> parent;\n\n  if (group) {\n    parent = group.toWeakRef();\n  }\n\n  auto serverItem = QSharedPointer<ServerItem>(\n      new ServerItem(treeModel.dynamicCast<ConnectionsTree::Operations>(),\n                     *static_cast<ConnectionsTree::Model*>(this), parent));\n  serverItem->setWeakPointer(serverItem.toWeakRef());\n\n  connect(\n      treeModel.data(), &TreeOperations::configUpdated, this,\n      [this, serverItem]() {\n        if (!serverItem) return;\n\n        emit itemChanged(\n            serverItem.dynamicCast<ConnectionsTree::TreeItem>().toWeakRef());\n      });\n\n  connect(treeModel.data(), &TreeOperations::filterHistoryUpdated,\n          this, [this]() {\n      saveConfig();\n  });\n\n  connect(serverItem.data(), &ConnectionsTree::ServerItem::editActionRequested,\n          this, [this, treeModel]() {\n            if (!treeModel) return;\n\n            auto config = treeModel->config();\n\n            emit connectionAboutToBeEdited(config.name());\n\n            // NOTE(u_glide): Do not show temproary stored password in the UI\n            if (config.askForSshPassword()) {\n                config.setSshPassword(QString());\n            }\n\n            emit editConnection(config);\n          });\n\n  connect(serverItem.data(),\n          &ConnectionsTree::ServerItem::deleteActionRequested, this,\n          [this, serverItem, treeModel, group]() {\n            if (!serverItem || !treeModel) return;\n\n            emit connectionAboutToBeEdited(treeModel->config().name());\n\n            if (group) {\n              group->removeConnection(serverItem);\n            } else {\n              removeRootItem(serverItem);\n            }\n\n            buildConnectionsCache();\n\n            emit sizeChanged();\n            saveConfig();\n          });\n\n  if (group) {\n    group->addServer(serverItem);\n  } else {\n    addRootItem(serverItem);\n  }\n}\n\nvoid ConnectionsManager::addGroup(\n    QSharedPointer<ConnectionsTree::ServerGroup> group) {\n  connect(group.data(), &ConnectionsTree::ServerGroup::editActionRequested,\n          this, [this, group]() {\n            if (!group) return;\n\n            ConnectionGroup g(group);\n\n            emit editConnectionGroup(g);\n          });\n\n  connect(group.data(), &ConnectionsTree::ServerGroup::deleteActionRequested,\n          this, [this, group]() {\n            if (!group) return;\n\n            removeRootItem(group);\n\n            buildConnectionsCache();\n\n            emit sizeChanged();\n            saveConfig();\n          });\n\n  addRootItem(group);\n\n  buildConnectionsCache();\n}\n\nvoid ConnectionsManager::buildConnectionsCache() {\n  m_connectionsCache.clear();\n\n  for (auto item : m_treeItems) {\n    if (item->type() == \"server_group\") {\n      QString nameTemplate = QString(\"[%1] %2\").arg(item->getDisplayName());\n\n      for (auto srv : item->getAllChilds()) {\n        QString name = nameTemplate.arg(srv->getDisplayName());\n        m_connectionsCache[name] =\n            srv.dynamicCast<ConnectionsTree::ServerItem>();\n      }\n    } else if (item->type() == \"server\") {\n      m_connectionsCache[item->getDisplayName()] =\n          item.dynamicCast<ConnectionsTree::ServerItem>();\n    }\n  }\n}\n"
  },
  {
    "path": "src/app/models/connectionsmanager.h",
    "content": "#pragma once\n#include <qredisclient/connection.h>\n\n#include <QSharedPointer>\n#include <functional>\n\n#include \"app/models/connectionconf.h\"\n#include \"bulk-operations/connections.h\"\n#include \"connections-tree/model.h\"\n#include \"treeoperations.h\"\n#include \"connectiongroup.h\"\n\nnamespace ValueEditor {\nclass TabsModel;\n}\n\nnamespace ConnectionsTree {\nclass ServerGroup;\n}\n\nclass Events;\n\nclass ConnectionsManager : public ConnectionsTree::Model,\n                           public BulkOperations::ConnectionsModel {\n  Q_OBJECT\n\n  Q_PROPERTY(int connectionsCount READ size NOTIFY sizeChanged)\n\n public:\n  ConnectionsManager(const QString& m_configPath,\n                     QSharedPointer<Events> events);  \n\n  void loadConnections();\n\n  Q_INVOKABLE void addNewConnection(const ServerConfig& config,\n                                    bool saveToConfig = true,\n                                    QSharedPointer<ConnectionsTree::ServerGroup> group =\n                                        QSharedPointer<ConnectionsTree::ServerGroup>());\n\n  Q_INVOKABLE void addNewGroup(const QString& name);\n\n  Q_INVOKABLE void updateGroup(const ConnectionGroup& group);\n\n  Q_INVOKABLE void updateConnection(const ServerConfig& config);\n\n  Q_INVOKABLE bool importConnections(const QString&);\n\n  Q_INVOKABLE bool saveConnectionsConfigToFile(const QString&);\n\n  Q_INVOKABLE void testConnectionSettings(const ServerConfig& config, QJSValue jsCallback);\n\n  Q_INVOKABLE void proceedWithConnectionSecret(const ServerConfig& config);\n\n  Q_INVOKABLE ServerConfig createEmptyConfig() const;\n\n  Q_INVOKABLE ServerConfig parseConfigFromRedisConnectionString(const QString&) const;\n\n  Q_INVOKABLE bool isRedisConnectionStringValid(const QString&);\n\n  void saveConfig();\n\n  Q_INVOKABLE int size() override;\n\n  // BulkOperations model methods\n  QSharedPointer<RedisClient::Connection> getByIndex(int index) override;\n\n  QStringList getConnections() override;\n\n  void applyGroupChanges() override;\n\n signals:\n  void editConnection(ServerConfig config);\n\n  void editConnectionGroup(ConnectionGroup group);\n\n  void connectionAboutToBeEdited(QString name);\n\n  void sizeChanged();\n\n  void connectionsLoaded();\n\n  void askUserForConnectionSecret(const ServerConfig& config, const QString& id);\n\n protected:\n  bool loadConnectionsConfigFromFile(const QString& config,\n                                     bool saveChangesToFile = false);\n\n  void tryToConnect(const ServerConfig& config, QJSValue jsCallback);\n\n private:\n  void createServerItemForConnection(const ServerConfig &config,\n      QSharedPointer<ConnectionsTree::ServerGroup> group=QSharedPointer<ConnectionsTree::ServerGroup>());\n\n  void addGroup(QSharedPointer<ConnectionsTree::ServerGroup> serverGroup);\n\n  void buildConnectionsCache();\n\n private:\n  QString m_configPath;\n  QSharedPointer<Events> m_events;\n  QJSValue m_jsCallback;\n  QMap<QString, QSharedPointer<ConnectionsTree::ServerItem>> m_connectionsCache;\n};\n"
  },
  {
    "path": "src/app/models/key-models/abstractkey.h",
    "content": "#pragma once\n#include <qredisclient/redisclient.h>\n#include <qredisclient/utils/text.h>\n#include <QByteArray>\n#include <QCoreApplication>\n#include <QDebug>\n\n#include <QPair>\n#include <QSharedPointer>\n#include <QString>\n#include <QVariant>\n#include \"modules/value-editor/keymodel.h\"\n#include \"rowcache.h\"\n#include \"app/models/connectionconf.h\"\n\ntemplate <typename T>\nclass KeyModel : public ValueEditor::Model {\n public:\n  KeyModel(QSharedPointer<RedisClient::Connection> connection,\n           QByteArray fullPath, int dbIndex, long long ttl,\n           QByteArray rowsCountCmd = QByteArray(),\n           QByteArray rowsLoadCmd = QByteArray())\n      : m_connection(connection),\n        m_keyFullPath(fullPath),\n        m_dbIndex(dbIndex),\n        m_ttl(ttl),\n        m_rowCount(0),\n        m_isMultiRow(!rowsCountCmd.isEmpty()),\n        m_rowsCountCmd(rowsCountCmd),\n        m_rowsLoadCmd(rowsLoadCmd),\n        m_scanCursor(0),\n        m_notifier(new ValueEditor::ModelSignals(), &QObject::deleteLater) {}\n\n  virtual QString getKeyName() override {\n    return printableString(m_keyFullPath);\n  }\n\n  virtual QString getKeyTitle(int limit=-1) override {\n    QString fullTitle = QString(\"%1::db%2::%3\")\n        .arg(m_connection->getConfig().name())\n        .arg(m_dbIndex)\n        .arg(getKeyName());\n\n    int length = fullTitle.size();\n\n    if (limit == -1 || length <= limit){\n        return fullTitle;\n    } else {\n        return QString(\"%1 ... %2\").arg(fullTitle.mid(0, limit/2)).arg(fullTitle.mid(length - limit/2));\n    }\n  }\n\n  virtual long long getTTL() override { return m_ttl; }\n\n  virtual bool isMultiRow() const override { return m_isMultiRow; }\n\n  virtual bool isRowLoaded(int rowIndex) override {\n    return m_rowsCache.isRowLoaded(rowIndex);\n  }\n\n  virtual unsigned long rowsCount() override {\n    if (isMultiRow())\n      return m_rowCount;\n    else\n      return 1;\n  }\n\n  virtual void setKeyName(const QByteArray& newKeyName,\n                          ValueEditor::Model::Callback c) override {\n    // NOTE(u_glide): DUMP + RESTORE + DEL is cluster compatible alternative to RENAME command\n    executeCmd(\n        {\"DUMP\", m_keyFullPath}, c,\n        [this, newKeyName](RedisClient::Response r, Callback c) {\n          executeCmd(\n              {\"RESTORE\", newKeyName,\n               QString::number(m_ttl > 0 ? m_ttl : 0).toLatin1(),\n               r.value().toByteArray()},\n              c,\n              [this, newKeyName](RedisClient::Response r, Callback c) {\n                if (!r.isOkMessage()) {\n                  return c(QCoreApplication::translate(\n                               \"RESP\", \"Cannot rename key %1: %2\")\n                               .arg(getKeyName())\n                               .arg(r.value().toString()));\n                }\n\n                executeCmd(\n                    {\"DEL\", m_keyFullPath}, [](const QString&) {},\n                    [](RedisClient::Response, Callback) {});\n\n                m_keyFullPath = newKeyName;\n                c(QString());\n              },\n              RedisClient::Response::Type::Status);\n        },\n        RedisClient::Response::Type::String);\n  }\n\n  virtual void setTTL(const long long ttl,\n                      ValueEditor::Model::Callback c) override {\n    executeCmd(\n        {\"EXPIRE\", m_keyFullPath, QString::number(ttl).toLatin1()}, c,\n        [this, ttl](RedisClient::Response r, Callback c) {\n          if (r.value().toInt() == 0) {\n            return c(\n                QCoreApplication::translate(\"RESP\", \"Cannot set TTL for key %1\")\n                    .arg(getKeyName()));\n          }\n\n          if (ttl >= 0)\n            m_ttl = ttl;\n          else\n            m_ttl = -1;\n\n          c(QString());\n        },\n        RedisClient::Response::Type::Integer);\n  }\n\n  virtual void persistKey(Callback c) override {\n    executeCmd(\n        {\"PERSIST\", m_keyFullPath}, c,\n        [this](RedisClient::Response r, Callback c) {\n          if (r.value().toInt() == 0) {\n            return c(QCoreApplication::translate(\n                         \"RESP\",\n                         \"Cannot persist key '%1'. <br> Key does not exist or \"\n                         \"does not have an assigned TTL value\")\n                         .arg(getKeyName()));\n          }\n\n          m_ttl = -1;\n\n          c(QString());\n        },\n        RedisClient::Response::Type::Integer);\n  }\n\n  virtual void removeKey(ValueEditor::Model::Callback c) override {\n    executeCmd({\"DEL\", m_keyFullPath}, c,\n               [this](RedisClient::Response, Callback c) {\n                 m_notifier->removed();\n                 c(QString());\n               });\n  }\n\n  virtual void loadRows(QVariant rowStart, unsigned long count,\n                        LoadRowsCallback callback) override {\n    if (m_rowsLoadCmd.mid(1, 4).toLower() == \"scan\") {\n      QList<QByteArray> cmdParts = {m_rowsLoadCmd, m_keyFullPath,\n                                    QString::number(m_scanCursor).toLatin1(),\n                                    \"COUNT\", QString::number(count).toLatin1()};\n\n      auto self = ValueEditor::Model::sharedFromThis().toWeakRef();\n\n      m_connection->cmd(\n          cmdParts, m_notifier.data(), -1,\n          [this, callback, rowStart, self](RedisClient::Response r) {\n            if (!r.isValidScanResponse()) {\n              callback(QCoreApplication::translate(\n                           \"RESP\", \"Cannot parse scan response\"),\n                       0);\n              return;\n            }\n\n            if (r.getCursor() > 0) {\n              m_scanCursor = r.getCursor();\n            }\n\n            try {\n              unsigned long addedRows =\n                  addLoadedRowsToCache(r.getCollection(), rowStart);\n              callback(QString(), addedRows);\n            } catch (const std::runtime_error& e) {\n              callback(QString(e.what()), 0);\n            }\n          },\n          [self, callback](QString err) {\n            if (!self) {\n              return;\n            }\n\n            return callback(\n                QCoreApplication::translate(\"RESP\", \"Connection error: \") + err,\n                0);\n          });\n\n    } else {\n      getRowsRange(\n          getRangeCmd(rowStart, count),\n          [this, callback, rowStart](const QString& err, QVariantList result) {\n            if (!err.isEmpty()) return callback(err, 0);\n\n            unsigned long addedRows = addLoadedRowsToCache(result, rowStart);\n            callback(QString(), addedRows);\n          });\n    }\n  }\n\n  virtual void clearRowCache() override { m_rowsCache.clear(); }\n\n  virtual QSharedPointer<ValueEditor::ModelSignals> getConnector()\n      const override {\n    return m_notifier;\n  }\n\n  virtual QSharedPointer<RedisClient::Connection> getConnection()\n      const override {\n    return m_connection;\n  }\n\n  virtual QString getDefaultFormatter() const override{\n      if (!m_connection)\n          return QString(\"auto\");\n\n      // TODO(u_glide): Pass ServerConfig to KeyModel and remove this\n      return m_connection->getConfig().getInternalParameters().value(\"default_formatter\", QString(\"auto\")).toString();\n  }\n\n  virtual unsigned int dbIndex() const override { return m_dbIndex; }\n\n  virtual void loadRowsCount(ValueEditor::Model::Callback c) override {\n    if (!isMultiRow()) {\n      m_rowCount = 1;\n      return c(QString());\n    }\n\n    executeCmd(\n        {m_rowsCountCmd, m_keyFullPath}, c,\n        [this](RedisClient::Response r, Callback c) {\n          m_rowCount = r.value().toUInt();\n          c(QString());\n        },\n        RedisClient::Response::Type::Integer);\n  }\n\n protected:\n  // multi row internal operations\n  virtual QList<QByteArray> getRangeCmd(QVariant rowStartId,\n                                        unsigned long count) {\n    QList<QByteArray> cmd;\n\n    unsigned long rowStart = rowStartId.toULongLong();\n    unsigned long rowEnd = std::min(m_rowCount, rowStart + count) - 1;\n\n    if (m_rowsLoadCmd.contains(' ')) {\n      QList<QByteArray> suffixCmd(m_rowsLoadCmd.split(' '));\n\n      cmd << suffixCmd.takeFirst();\n      cmd << m_keyFullPath << QString::number(rowStart).toLatin1()\n          << QString::number(rowEnd).toLatin1();\n      cmd += suffixCmd;\n\n    } else {\n      cmd << m_rowsLoadCmd << m_keyFullPath\n          << QString::number(rowStart).toLatin1()\n          << QString::number(rowEnd).toLatin1();\n    }\n    return cmd;\n  }\n\n  virtual void getRowsRange(\n      const QList<QByteArray>& rangeCmd,\n      std::function<void(const QString&, QVariantList)> callback) {\n    try {\n      m_connection->command(\n          rangeCmd, getConnector().data(),\n          [this, callback](RedisClient::Response r, QString e) {\n            if (!e.isEmpty()) {\n              return callback(e, QVariantList());\n            }\n\n            if (r.type() != RedisClient::Response::Array) {\n              return callback(QCoreApplication::translate(\n                                  \"RESP\", \"Cannot load rows for key %1: %2\")\n                                  .arg(getKeyName()),\n                              QVariantList());\n            }\n\n            return callback(QString(), r.value().toList());\n          },\n          -1);\n    } catch (const RedisClient::Connection::Exception& e) {\n      callback(\n          QCoreApplication::translate(\"RESP\", \"Cannot load rows for key %1: %2\")\n              .arg(getKeyName())\n              .arg(e.what()),\n          QVariantList());\n    }\n  }\n\n  // row validator\n  virtual bool isRowValid(const QVariantMap& row) {\n    if (row.isEmpty()) return false;\n\n    QSet<QString> validKeys;\n\n    foreach (QByteArray role, getRoles().values()) { validKeys.insert(role); }\n\n    QMapIterator<QString, QVariant> i(row);\n\n    while (i.hasNext()) {\n      i.next();\n\n      if (!validKeys.contains(i.key())) return false;\n    }\n\n    return true;\n  }\n\n  virtual void setRemovedIfEmpty() {\n    if (m_rowCount == 0) {\n      m_notifier->removed();\n    }\n  }\n\n  typedef std::function<void(RedisClient::Response r, Callback c)> CmdHandler;\n\n  virtual void executeCmd(QList<QByteArray> cmd, Callback c,\n                          CmdHandler handler = CmdHandler(),\n                          RedisClient::Response::Type expectedType =\n                              RedisClient::Response::Type::Unknown) {\n    m_connection->cmd(\n        cmd, m_notifier.data(), -1,\n        [c, handler, expectedType](RedisClient::Response r) {\n          if (expectedType != RedisClient::Response::Type::Unknown &&\n              r.type() != expectedType) {\n            return c(QCoreApplication::translate(\n                         \"RESP\", \"Server returned unexpected response: \") +\n                     r.value().toString());\n          }\n\n          if (handler) {\n            return handler(r, c);\n          } else {\n            return c(QString());\n          }\n        },\n        [c](QString err) {\n          return c(QCoreApplication::translate(\"RESP\", \"Connection error: \") +\n                   err);\n        });\n  }\n\n  virtual int addLoadedRowsToCache(const QVariantList& rows,\n                                   QVariant rowStart) = 0;\n\n  QVariant filter(const QString& key) const override {\n    return m_filters.value(key, QVariant());\n  };\n\n  void setFilter(const QString& k, QVariant v) override {\n      m_filters[k] = v;\n      qDebug() << \"filter:\" << k << v;\n  }\n\n protected:\n  QSharedPointer<RedisClient::Connection> m_connection;\n  QByteArray m_keyFullPath;\n  int m_dbIndex;\n  long long m_ttl;\n  unsigned long m_rowCount;\n  bool m_isMultiRow;\n\n  // CMD strings\n  QByteArray m_rowsCountCmd;\n  QByteArray m_rowsLoadCmd;\n\n  MappedCache<T> m_rowsCache;\n  long long m_scanCursor;\n  QSharedPointer<ValueEditor::ModelSignals> m_notifier;\n\n  QVariantMap m_filters;\n};\n"
  },
  {
    "path": "src/app/models/key-models/bfkey.cpp",
    "content": "#include \"bfkey.h\"\n\n#include <QJsonDocument>\n\nBloomFilterKeyModel::BloomFilterKeyModel(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray fullPath,\n    int dbIndex, long long ttl, QString filterFamily)\n    : KeyModel(connection, fullPath, dbIndex, ttl), m_type(filterFamily) {}\n\nQString BloomFilterKeyModel::type() { return m_type; }\n\nQStringList BloomFilterKeyModel::getColumnNames() {\n  return QStringList() << \"value\";\n}\n\nQHash<int, QByteArray> BloomFilterKeyModel::getRoles() {\n  QHash<int, QByteArray> roles;\n  roles[Roles::Value] = \"value\";\n  return roles;\n}\n\nQVariant BloomFilterKeyModel::getData(int rowIndex, int dataRole) {\n  if (rowIndex > 0 || !isRowLoaded(rowIndex)) return QVariant();\n  if (dataRole == Roles::Value)\n    return QJsonDocument::fromVariant(m_rowsCache[rowIndex])\n        .toJson(QJsonDocument::Compact);\n\n  return QVariant();\n}\n\nvoid BloomFilterKeyModel::addRow(const QVariantMap& row, Callback c) {\n  QByteArray value = row.value(\"value\").toByteArray();\n\n  executeCmd({QString(\"%1.ADD\").arg(m_type).toLatin1(), m_keyFullPath, value},\n             [this, c](const QString& err) {\n               m_rowCount++;\n               return c(err);\n             });\n}\n\nvoid BloomFilterKeyModel::loadRows(QVariant, unsigned long,\n                                   LoadRowsCallback callback) {\n  auto onConnectionError = [callback](const QString& err) {\n    return callback(err, 0);\n  };\n\n  auto responseHandler = [this, callback](const RedisClient::Response& r, Callback) {\n    m_rowsCache.clear();\n    auto value = r.value().toList();\n\n    QVariantMap row;\n\n    for (auto item = value.cbegin(); item != value.cend(); ++item) {\n      auto key = item->toByteArray();\n      ++item;\n\n      if (item == value.cend()) {\n        emit m_notifier->error(QCoreApplication::translate(\n            \"RESP\", \"Data was loaded from server partially.\"));\n        break;\n      }\n\n      auto keyVal = item->toByteArray();\n      row[key] = keyVal;\n    }\n\n    m_rowsCache.push_back(row);\n    callback(QString(), 1);\n  };\n\n  executeCmd({QString(\"%1.INFO\").arg(m_type).toLatin1(), m_keyFullPath},\n             onConnectionError, responseHandler, RedisClient::Response::Array);\n}\n"
  },
  {
    "path": "src/app/models/key-models/bfkey.h",
    "content": "#pragma once\n#include \"stringkey.h\"\n\nclass BloomFilterKeyModel : public KeyModel<QVariant> {\n public:\n  BloomFilterKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                      QByteArray fullPath, int dbIndex, long long ttl, QString filterFamily=\"bf\");\n\n  QString type() override;\n  QStringList getColumnNames() override;\n  QHash<int, QByteArray> getRoles() override;\n  QVariant getData(int rowIndex, int dataRole) override;\n\n  void addRow(const QVariantMap&, Callback c) override;\n\n  virtual void updateRow(int, const QVariantMap&,\n                         Callback) override {\n      // NOTE(u_glide): BF/CF is read-only\n  }\n  void loadRows(QVariant, unsigned long, LoadRowsCallback callback) override;\n\n  void removeRow(int, Callback) override {\n      // NOTE(u_glide): BF/CF is read-only\n  }\n\n  virtual unsigned long rowsCount() override { return m_rowCount; }\n\n protected:\n  int addLoadedRowsToCache(const QVariantList&, QVariant) override { return 1; }\n\n private:\n  enum Roles { Value = Qt::UserRole + 1 };\n  QString m_type;\n};\n"
  },
  {
    "path": "src/app/models/key-models/hashkey.cpp",
    "content": "#include \"hashkey.h\"\n#include <qredisclient/connection.h>\n#include <QObject>\n\nHashKeyModel::HashKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                           QByteArray fullPath, int dbIndex, long long ttl)\n    : KeyModel(connection, fullPath, dbIndex, ttl, \"HLEN\", \"HSCAN\") {}\n\nQString HashKeyModel::type() { return \"hash\"; }\n\nQStringList HashKeyModel::getColumnNames() {\n  return QStringList() << \"rowNumber\"\n                       << \"key\"\n                       << \"value\";\n}\n\nQHash<int, QByteArray> HashKeyModel::getRoles() {\n  QHash<int, QByteArray> roles;\n  roles[Roles::RowNumber] = \"rowNumber\";\n  roles[Roles::Key] = \"key\";\n  roles[Roles::Value] = \"value\";\n  return roles;\n}\n\nQVariant HashKeyModel::getData(int rowIndex, int dataRole) {\n  if (!isRowLoaded(rowIndex)) return QVariant();\n\n  QPair<QByteArray, QByteArray> row = m_rowsCache[rowIndex];\n\n  if (dataRole == Roles::Key)\n    return row.first;\n  else if (dataRole == Roles::Value)\n    return row.second;\n  else if (dataRole == Roles::RowNumber)\n    return rowIndex;\n\n  return QVariant();\n}\n\nvoid HashKeyModel::updateRow(int rowIndex, const QVariantMap &row, Callback c) {\n  if (!isRowLoaded(rowIndex) || !isRowValid(row)) {\n    c(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n    return;\n  }\n\n  QPair<QByteArray, QByteArray> cachedRow = m_rowsCache[rowIndex];\n\n  bool keyChanged = cachedRow.first != row[\"key\"].toByteArray();\n  bool valueChanged = cachedRow.second != row[\"value\"].toByteArray();\n\n  QPair<QByteArray, QByteArray> newRow(\n      (keyChanged) ? row[\"key\"].toByteArray() : cachedRow.first,\n      (valueChanged) ? row[\"value\"].toByteArray() : cachedRow.second);\n\n  auto afterValueUpdate = [this, c, rowIndex, newRow](const QString &err) {\n    if (err.isEmpty()) m_rowsCache.replace(rowIndex, newRow);\n\n    return c(err);\n  };\n\n  if (keyChanged) {\n    deleteHashRow(cachedRow.first,\n                  [this, c, newRow, afterValueUpdate](const QString &err) {\n                    if (err.size() > 0) return c(err);\n\n                    setHashRow(newRow.first, newRow.second, afterValueUpdate);\n                  });\n  } else {\n    setHashRow(newRow.first, newRow.second, afterValueUpdate);\n  }\n}\n\nvoid HashKeyModel::addRow(const QVariantMap &row, Callback c) {\n  if (!isRowValid(row)) {\n    c(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n    return;\n  }\n\n  setHashRow(\n      row[\"key\"].toByteArray(), row[\"value\"].toByteArray(),\n      [this, c](const QString &err) {\n        if (err.isEmpty()) m_rowCount++;\n        return c(err);\n      },\n      false);\n}\n\nvoid HashKeyModel::removeRow(int i, Callback c) {\n  if (!isRowLoaded(i)) return;\n\n  QPair<QByteArray, QByteArray> row = m_rowsCache[i];\n\n  deleteHashRow(row.first, [this, i, c](const QString &err) {\n    if (err.isEmpty()) {\n      m_rowCount--;\n      m_rowsCache.removeAt(i);\n      setRemovedIfEmpty();\n    }\n\n    return c(err);\n  });\n}\n\nvoid HashKeyModel::setHashRow(const QByteArray &hashKey,\n                              const QByteArray &hashValue, Callback c,\n                              bool updateIfNotExist) {\n  QList<QByteArray> rawCmd{(updateIfNotExist) ? \"HSET\" : \"HSETNX\",\n                           m_keyFullPath, hashKey, hashValue};\n\n  executeCmd(rawCmd, c,\n             [updateIfNotExist](RedisClient::Response r, Callback c) {\n               if (updateIfNotExist == false && r.value().toInt() == 0) {\n                 return c(QCoreApplication::translate(\n                     \"RESP\", \"Value with the same key already exists\"));\n               } else {\n                 return c(QString());\n               }\n             });\n}\n\nvoid HashKeyModel::deleteHashRow(const QByteArray &hashKey, Callback c) {\n  executeCmd({\"HDEL\", m_keyFullPath, hashKey}, c);\n}\n\nint HashKeyModel::addLoadedRowsToCache(const QVariantList &rows,\n                                       QVariant rowStartId) {\n  QList<QPair<QByteArray, QByteArray>> result;\n\n  for (QVariantList::const_iterator item = rows.begin(); item != rows.end();\n       ++item) {\n    QPair<QByteArray, QByteArray> value;\n    value.first = item->toByteArray();\n    ++item;\n\n    if (item == rows.end()) {\n      emit m_notifier->error(QCoreApplication::translate(\n          \"RESP\", \"Data was loaded from server partially.\"));\n      return 0;\n    }\n\n    value.second = item->toByteArray();\n    result.push_back(value);\n  }\n\n  auto rowStart = rowStartId.toLongLong();\n  m_rowsCache.addLoadedRange({rowStart, rowStart + result.size() - 1}, result);\n\n  return result.size();\n}\n"
  },
  {
    "path": "src/app/models/key-models/hashkey.h",
    "content": "#pragma once\n#include \"abstractkey.h\"\n\nclass HashKeyModel : public KeyModel<QPair<QByteArray, QByteArray>> {\n public:\n  HashKeyModel(QSharedPointer<RedisClient::Connection> connection,\n               QByteArray fullPath, int dbIndex, long long ttl);\n\n  QString type() override;\n  QStringList getColumnNames() override;\n  QHash<int, QByteArray> getRoles() override;\n\n  QVariant getData(int rowIndex, int dataRole) override;\n  void addRow(const QVariantMap &, Callback) override;\n  virtual void updateRow(int rowIndex, const QVariantMap &, Callback) override;\n  void removeRow(int, Callback) override;\n\n protected:\n  int addLoadedRowsToCache(const QVariantList &list,\n                           QVariant rowStart) override;\n\n private:\n  enum Roles { RowNumber = Qt::UserRole + 1, Key, Value };\n\n  void setHashRow(const QByteArray &hashKey, const QByteArray &hashValue,\n                  Callback c, bool updateIfNotExist = true);\n  void deleteHashRow(const QByteArray &hashKey, Callback c);\n};\n"
  },
  {
    "path": "src/app/models/key-models/keyfactory.cpp",
    "content": "#include \"keyfactory.h\"\n\n#include <qredisclient/redisclient.h>\n#include <qredisclient/utils/text.h>\n\n#include <QFile>\n#include <QObject>\n\n#include \"bfkey.h\"\n#include \"hashkey.h\"\n#include \"listkey.h\"\n#include \"rejsonkey.h\"\n#include \"setkey.h\"\n#include \"sortedsetkey.h\"\n#include \"stream.h\"\n#include \"stringkey.h\"\n#include \"unknownkey.h\"\n\nKeyFactory::KeyFactory() {}\n\nvoid KeyFactory::loadKey(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray keyFullPath,\n    int dbIndex,\n    std::function<void(QSharedPointer<ValueEditor::Model>, const QString&)>\n        callback) {\n  auto processError = [callback, keyFullPath](const QString& err) {\n    QString msg(QCoreApplication::translate(\n        \"RESP\", \"Cannot load key %1, connection error occurred: %2\"));\n    callback(QSharedPointer<ValueEditor::Model>(),\n             msg.arg(printableString(keyFullPath)).arg(err));\n  };\n\n  auto loadModel = [this, connection, keyFullPath, dbIndex, callback,\n                    processError](RedisClient::Response resp) {\n    QSharedPointer<ValueEditor::Model> result;\n\n    if (resp.isErrorMessage() ||\n        resp.type() != RedisClient::Response::Type::Status) {\n      QString msg(QCoreApplication::translate(\n          \"RESP\", \"Cannot load key %1, connection error occurred: %2\"));\n      callback(\n          result,\n          msg.arg(printableString(keyFullPath)).arg(resp.value().toString()));\n      return;\n    }\n\n    QString type = resp.value().toString();\n\n    if (type == \"none\") {\n      QString msg(QCoreApplication::translate(\n          \"RESP\",\n          \"Cannot load key %1 because it doesn't exist in database.\"\n          \" Please reload connection tree and try again.\"));\n      callback(result, msg.arg(printableString(keyFullPath)));\n      return;\n    }\n\n    auto parseTtl = [this, type, connection, keyFullPath, dbIndex, callback,\n                     processError](const RedisClient::Response& ttlResult) {\n      long long ttl = -1;\n\n      if (ttlResult.type() == RedisClient::Response::Integer) {\n        ttl = ttlResult.value().toLongLong();\n      }\n\n      auto result = createModel(type, connection, keyFullPath, dbIndex, ttl);\n\n      callback(result, QString());\n    };\n\n    connection->cmd({\"ttl\", keyFullPath}, this, -1, parseTtl, processError);\n  };\n\n  try {\n    connection->cmd({\"type\", keyFullPath}, this, dbIndex, loadModel,\n                    processError);\n  } catch (const RedisClient::Connection::Exception& e) {\n    callback(QSharedPointer<ValueEditor::Model>(),\n             QCoreApplication::translate(\"RESP\",\n                                         \"Cannot retrieve type of the key: \") +\n                 QString(e.what()));\n  }\n}\n\nvoid KeyFactory::createNewKeyRequest(\n    QSharedPointer<RedisClient::Connection> connection,\n    QSharedPointer<ConnectionsTree::Operations::OpenNewKeyDialogCallback> callback,\n    int dbIndex, QString keyPrefix) {\n  if (connection.isNull() || dbIndex < 0) return;\n  emit newKeyDialog(NewKeyRequest(connection, dbIndex, callback, keyPrefix));\n}\n\nvoid KeyFactory::submitNewKeyRequest(NewKeyRequest r) {\n  QSharedPointer<ValueEditor::Model> result = createModel(\n      r.keyType(), r.connection(), r.keyName().toUtf8(), r.dbIndex(), -1);\n\n  if (!result) return;\n\n  auto onRowAdded = [this, r, result](const QString& err) {\n    if (err.size() > 0) {\n      emit error(err);\n      return;\n    }\n\n    r.callback();\n    emit keyAdded();\n  };\n\n  r.connection()->cmd(\n      {\"PING\"}, this, r.dbIndex(),\n      [onRowAdded, result, r](const RedisClient::Response& resp) {\n        auto testResp = resp.value().toByteArray();\n        if (testResp != \"PONG\") {\n          return onRowAdded(testResp);\n        }\n\n        auto val = r.value();\n\n        if (!r.valueFilePath().isEmpty() && QFile::exists(r.valueFilePath())) {\n          QFile valueFile(r.valueFilePath());\n\n          if (!valueFile.open(QIODevice::ReadOnly)) {\n            return onRowAdded(QCoreApplication::translate(\n                \"RESP\", \"Cannot open file with key value\"));\n          }\n\n          val[\"value\"] = valueFile.readAll();\n        }\n\n        result->addRow(val, onRowAdded);\n      },\n      onRowAdded);\n}\n\nQSharedPointer<ValueEditor::Model> KeyFactory::createModel(\n    QString type, QSharedPointer<RedisClient::Connection> connection,\n    QByteArray keyFullPath, int dbIndex, long long ttl) {\n  if (type == \"string\") {\n    return QSharedPointer<ValueEditor::Model>(\n        new StringKeyModel(connection, keyFullPath, dbIndex, ttl));\n  } else if (type == \"list\") {\n    return QSharedPointer<ValueEditor::Model>(\n        new ListKeyModel(connection, keyFullPath, dbIndex, ttl));\n  } else if (type == \"set\") {\n    return QSharedPointer<ValueEditor::Model>(\n        new SetKeyModel(connection, keyFullPath, dbIndex, ttl));\n  } else if (type == \"zset\") {\n    return QSharedPointer<ValueEditor::Model>(\n        new SortedSetKeyModel(connection, keyFullPath, dbIndex, ttl));\n  } else if (type == \"hash\") {\n    return QSharedPointer<ValueEditor::Model>(\n        new HashKeyModel(connection, keyFullPath, dbIndex, ttl));\n  } else if (type == \"ReJSON-RL\" || type == \"ReJSON\") {\n    return QSharedPointer<ValueEditor::Model>(\n        new ReJSONKeyModel(connection, keyFullPath, dbIndex, ttl));\n  } else if (type == \"stream\") {\n    return QSharedPointer<ValueEditor::Model>(\n        new StreamKeyModel(connection, keyFullPath, dbIndex, ttl));\n  } else if (type.startsWith(\"MBbloom\")) {\n      QString ff = type.endsWith(\"CF\")? \"cf\" : \"bf\";\n      return QSharedPointer<ValueEditor::Model>(\n          new BloomFilterKeyModel(connection, keyFullPath, dbIndex, ttl, ff));\n  }\n\n  return QSharedPointer<ValueEditor::Model>(\n      new UnknownKeyModel(connection, keyFullPath, dbIndex, ttl, type));\n}\n"
  },
  {
    "path": "src/app/models/key-models/keyfactory.h",
    "content": "#pragma once\r\n#include <QJSValue>\r\n#include \"exception.h\"\r\n#include \"modules/value-editor/abstractkeyfactory.h\"\r\n#include \"newkeyrequest.h\"\r\n#include \"modules/connections-tree/operations.h\"\r\n\r\nclass KeyFactory : public QObject, public ValueEditor::AbstractKeyFactory {\r\n  Q_OBJECT\r\n public:\r\n  KeyFactory();\r\n\r\n  void loadKey(\r\n      QSharedPointer<RedisClient::Connection> connection,\r\n      QByteArray keyFullPath, int dbIndex,\r\n      std::function<void(QSharedPointer<ValueEditor::Model>, const QString&)>\r\n          callback) override;\r\n\r\n public slots:\r\n  void createNewKeyRequest(\r\n      QSharedPointer<RedisClient::Connection> connection,\r\n      QSharedPointer<ConnectionsTree::Operations::OpenNewKeyDialogCallback>\r\n          callback,\r\n      int dbIndex, QString keyPrefix);\r\n\r\n  void submitNewKeyRequest(NewKeyRequest r);\r\n\r\n signals:\r\n  void newKeyDialog(NewKeyRequest r);\r\n  void keyAdded();\r\n  void error(const QString& err);\r\n\r\n private:\r\n  QSharedPointer<ValueEditor::Model> createModel(\r\n      QString type, QSharedPointer<RedisClient::Connection> connection,\r\n      QByteArray keyFullPath, int dbIndex, long long ttl);\r\n};\r\n"
  },
  {
    "path": "src/app/models/key-models/listkey.cpp",
    "content": "#include \"listkey.h\"\n#include <qredisclient/connection.h>\n\nconst static QByteArray LIST_ITEM_REMOVAL_STUB(\"---VALUE_REMOVED_BY_RESP_APP---\");\n\nListKeyModel::ListKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                           QByteArray fullPath, int dbIndex, long long ttl)\n    : ListLikeKeyModel(connection, fullPath, dbIndex, ttl, \"LLEN\", \"LRANGE\") {}\n\nQString ListKeyModel::type() { return \"list\"; }\n\nvoid ListKeyModel::updateRow(int rowIndex, const QVariantMap &row, Callback c) {\n  if (!isRowLoaded(rowIndex) || !isRowValid(row)) {\n    return c(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n  }\n\n  int dbRowIndex = rowIndex;\n\n  if (isReverseOrder()) {\n    dbRowIndex = -rowIndex - 1;\n  }\n\n  QByteArray newRow(row[\"value\"].toByteArray());\n\n  auto afterRowUpdate = [this, rowIndex, newRow, c](const QString &err) {\n    if (err.isEmpty()) m_rowsCache.replace(rowIndex, newRow);\n\n    return c(err);\n  };\n\n  verifyListItemPosition(dbRowIndex, [this, dbRowIndex, c, newRow,\n                                       afterRowUpdate](const QString &err) {\n    if (err.size() > 0) return c(err);\n\n    setListRow(dbRowIndex, newRow, afterRowUpdate);\n  });\n}\n\nvoid ListKeyModel::addRow(const QVariantMap &row, Callback c) {\n  if (!isRowValid(row)) {\n    emit m_notifier->error(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n    return;\n  }\n\n  addListRow(row[\"value\"].toByteArray(), [this, c](const QString &err) {\n    if (err.isEmpty()) m_rowCount++;\n\n    return c(err);\n  });\n}\n\nvoid ListKeyModel::removeRow(int i, ValueEditor::Model::Callback c) {\n  if (!isRowLoaded(i)) return;\n\n  auto onItemRemoval = [this, c, i](const QString &err) {\n    if (err.isEmpty()) {\n      m_rowCount--;\n      m_rowsCache.removeAt(i);\n      setRemovedIfEmpty();\n    };\n\n    return c(err);\n  };\n\n  auto onItemHidding = [this, c, onItemRemoval](const QString &err) {\n    if (err.size() > 0) return c(err);\n\n    // Remove all system values from list\n    deleteListRow(0, LIST_ITEM_REMOVAL_STUB, onItemRemoval);\n  };\n\n  int dbRowIndex = i;\n\n  if (isReverseOrder()) {\n    dbRowIndex = -i - 1;\n  }\n\n  verifyListItemPosition(\n      dbRowIndex, [this, dbRowIndex, c, onItemHidding](const QString &err) {\n        if (err.size() > 0) return c(err);\n\n        // Replace value by system string\n        setListRow(dbRowIndex, LIST_ITEM_REMOVAL_STUB, onItemHidding);\n      });\n}\n\nQList<QByteArray> ListKeyModel::getRangeCmd(QVariant rowStartId, unsigned long count)\n{\n    if (isReverseOrder()) {\n        long rowStart = -rowStartId.toLongLong() - 1;\n        long rowStop = rowStart - count + 1;\n\n        QList<QByteArray> cmd {m_rowsLoadCmd, m_keyFullPath,\n                              QString::number(rowStop).toLatin1(),\n                              QString::number(rowStart).toLatin1()};\n        return cmd;\n    } else {\n        return KeyModel::getRangeCmd(rowStartId, count);\n    }\n}\n\nint ListKeyModel::addLoadedRowsToCache(const QVariantList &rows,\n                                       QVariant rowStartId) {\n  if (isReverseOrder()) {\n    return ListLikeKeyModel::addLoadedRowsToCache(\n        QList<QVariant>(rows.rbegin(), rows.rend()), rowStartId);\n  } else {\n    return ListLikeKeyModel::addLoadedRowsToCache(rows, rowStartId);\n  }\n}\n\nvoid ListKeyModel::verifyListItemPosition(int row, Callback c) {\n  auto verifyResponse = [this, row](RedisClient::Response r, Callback c) {\n    QVariantList currentState = r.value().toList();        \n    QByteArray cachedValue;\n\n    if (isReverseOrder()) {\n        cachedValue = m_rowsCache[-row - 1];\n    } else {\n        cachedValue = m_rowsCache[row];\n    }\n\n    bool isChanged = currentState.size() != 1 ||\n                     currentState[0].toByteArray() != QString(cachedValue);\n\n    if (isChanged) {\n      return c(QCoreApplication::translate(\"RESP\",\n                                           \"The row has been changed on server.\"\n                                           \"Reload and try again.\"));\n    } else {\n      return c(QString());\n    }\n  };\n\n  executeCmd({\"LRANGE\", m_keyFullPath, QString::number(row).toLatin1(),\n              QString::number(row).toLatin1()},\n             c, verifyResponse);\n}\n\nvoid ListKeyModel::addListRow(const QByteArray &value, Callback c) {\n  executeCmd({\"LPUSH\", m_keyFullPath, value}, c);\n}\n\nvoid ListKeyModel::setListRow(int pos, const QByteArray &value, Callback c) {\n  executeCmd({\"LSET\", m_keyFullPath, QString::number(pos).toLatin1(), value},\n             c);\n}\n\nvoid ListKeyModel::deleteListRow(int count, const QByteArray &value,\n                                 Callback c) {\n  executeCmd({\"LREM\", m_keyFullPath, QString::number(count).toLatin1(), value},\n             c);\n}\n\nbool ListKeyModel::isReverseOrder() const\n{\n    return m_filters.value(\"order\", \"default\") == \"reverse\";\n}\n"
  },
  {
    "path": "src/app/models/key-models/listkey.h",
    "content": "#pragma once\n#include <QByteArray>\n#include \"listlikekey.h\"\n\nclass ListKeyModel : public ListLikeKeyModel {\n public:\n  ListKeyModel(QSharedPointer<RedisClient::Connection> connection,\n               QByteArray fullPath, int dbIndex, long long ttl);\n\n  QString type() override;  \n\n  void addRow(const QVariantMap &, ValueEditor::Model::Callback c) override;\n  virtual void updateRow(int rowIndex, const QVariantMap &,\n                         ValueEditor::Model::Callback c) override;\n  void removeRow(int, ValueEditor::Model::Callback c) override;\n\nprotected:\n  virtual QList<QByteArray> getRangeCmd(QVariant rowStartId,\n                                        unsigned long count) override;\n\n  int addLoadedRowsToCache(const QVariantList& rows,\n                           QVariant rowStart) override;\n\n\n private:\n  void verifyListItemPosition(int row, Callback c);\n  void addListRow(const QByteArray &value, Callback c);\n  void setListRow(int pos, const QByteArray &value, Callback c);\n  void deleteListRow(int count, const QByteArray &value, Callback c);\n\n  bool isReverseOrder() const;\n};\n"
  },
  {
    "path": "src/app/models/key-models/listlikekey.cpp",
    "content": "#include \"listlikekey.h\"\n\nListLikeKeyModel::ListLikeKeyModel(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray fullPath,\n    int dbIndex, long long ttl, QByteArray rowsCountCmd, QByteArray rowsLoadCmd)\n    : KeyModel(connection, fullPath, dbIndex, ttl, rowsCountCmd, rowsLoadCmd) {}\n\nQStringList ListLikeKeyModel::getColumnNames() {\n  return QStringList() << \"rowNumber\"\n                       << \"value\";\n}\n\nQHash<int, QByteArray> ListLikeKeyModel::getRoles() {\n  QHash<int, QByteArray> roles;\n  roles[Roles::Value] = \"value\";\n  roles[Roles::RowNumber] = \"rowNumber\";\n  return roles;\n}\n\nQVariant ListLikeKeyModel::getData(int rowIndex, int dataRole) {\n  if (!isRowLoaded(rowIndex)) return QVariant();\n\n  switch (dataRole) {\n    case Value:\n      return m_rowsCache[rowIndex];\n    case RowNumber:\n      return rowIndex;\n  }\n\n  return QVariant();\n}\n\nint ListLikeKeyModel::addLoadedRowsToCache(const QVariantList &rows,\n                                           QVariant rowStartId) {\n  QList<QByteArray> result;\n  auto rowStart = rowStartId.toLongLong();\n\n  foreach (QVariant row, rows) { result.push_back(row.toByteArray()); }\n\n  m_rowsCache.addLoadedRange({rowStart, rowStart + result.size() - 1}, result);\n  return result.size();\n}\n"
  },
  {
    "path": "src/app/models/key-models/listlikekey.h",
    "content": "#pragma once\n#include \"abstractkey.h\"\n\nclass ListLikeKeyModel : public KeyModel<QByteArray> {\n public:\n  ListLikeKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                   QByteArray fullPath, int dbIndex, long long ttl,\n                   QByteArray rowsCountCmd, QByteArray rowsLoadCmd);\n\n  QStringList getColumnNames() override;\n  QHash<int, QByteArray> getRoles() override;\n  QVariant getData(int rowIndex, int dataRole) override;\n\n protected:\n  enum Roles { RowNumber = Qt::UserRole + 1, Value };\n\n protected:\n  int addLoadedRowsToCache(const QVariantList& rows,\n                           QVariant rowStart) override;\n};\n"
  },
  {
    "path": "src/app/models/key-models/newkeyrequest.cpp",
    "content": "#include \"newkeyrequest.h\"\n\nNewKeyRequest::NewKeyRequest(QSharedPointer<RedisClient::Connection> connection, int dbIndex, QSharedPointer<ConnectionsTree::Operations::OpenNewKeyDialogCallback> callback, QString keyPrefix)\n    : m_connection(connection),\n      m_dbIndex(dbIndex),\n      m_callback(callback),\n      m_keyName(keyPrefix),\n      m_valueFilePath(QString()){\n}\n\nvoid NewKeyRequest::loadAdditionalKeyTypesInfo(QJSValue jsCallback) {\n    if (!m_connection) {\n        qWarning() << \"Invalid connection\";\n        return;\n    }\n\n    m_jsCallback = jsCallback;\n\n    qDebug() << m_jsCallback.isCallable();\n\n    m_connection->refreshServerInfo([this](){\n        if (!m_jsCallback.isCallable()) {\n            qDebug() << \"JS callback is not callable\";\n            return;\n        }\n\n        if (!m_connection) {\n            m_jsCallback.call(QJSValueList{});\n            return;\n        }\n\n        auto loadedModules = m_connection->getEnabledModules().keys();\n\n        QJSValueList supportedKeyTypesExposedByModules;\n\n        for (QString mod : loadedModules) {\n            if (mod == \"ReJSON\")\n                supportedKeyTypesExposedByModules.append(QJSValue(mod));\n        }\n\n        m_jsCallback.call(supportedKeyTypesExposedByModules);\n    });\n}\n"
  },
  {
    "path": "src/app/models/key-models/newkeyrequest.h",
    "content": "#pragma once\n#include <qredisclient/connection.h>\n#include <QObject>\n#include <QSharedPointer>\n#include <functional>\n#include <QJSValue>\n#include \"modules/connections-tree/operations.h\"\n\nclass NewKeyRequest {\n  Q_GADGET\n\n  Q_PROPERTY(QString dbIdString READ dbIdString)\n  Q_PROPERTY(QString keyName READ keyName WRITE setKeyName)\n  Q_PROPERTY(QString keyType READ keyType WRITE setKeyType)\n  Q_PROPERTY(QVariantMap value READ value WRITE setValue)\n  Q_PROPERTY(QString valueFilePath READ valueFilePath WRITE setValueFilePath)  \n\n public:\n  NewKeyRequest(QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n                QSharedPointer<ConnectionsTree::Operations::OpenNewKeyDialogCallback> callback,\n                QString keyPrefix = QString());\n\n  NewKeyRequest() {}\n\n  QString dbIdString() {\n    return QString(\"%1:db%2\")\n        .arg(m_connection->getConfig().name())\n        .arg(m_dbIndex);\n  }\n\n  int dbIndex() { return m_dbIndex; }\n\n  QString keyName() { return m_keyName; }\n\n  void setKeyName(QString k) { m_keyName = k; }\n\n  QString keyType() { return m_keyType; }\n\n  void setKeyType(QString k) { m_keyType = k; }\n\n  QVariantMap value() const { return m_value; }\n\n  void setValue(const QVariantMap& v) { m_value = v; }\n\n  QString valueFilePath() const { return m_valueFilePath; }\n\n  void setValueFilePath(const QString& path) { m_valueFilePath = path; }\n\n  QSharedPointer<RedisClient::Connection> connection() { return m_connection; }\n\n  void callback() const {\n    if (m_callback) m_callback->call();\n  }\n\n  Q_INVOKABLE void loadAdditionalKeyTypesInfo(QJSValue jsCallback);\n\n private:\n  QSharedPointer<RedisClient::Connection> m_connection = nullptr;\n  int m_dbIndex = -1;\n  QSharedPointer<ConnectionsTree::Operations::OpenNewKeyDialogCallback> m_callback;\n  QJSValue m_jsCallback;\n  QString m_keyName;\n  QString m_keyType;\n  QVariantMap m_value;\n  QString m_valueFilePath;\n};\n\nQ_DECLARE_METATYPE(NewKeyRequest)\n"
  },
  {
    "path": "src/app/models/key-models/rejsonkey.cpp",
    "content": "#include \"rejsonkey.h\"\n#include <qredisclient/connection.h>\n\nReJSONKeyModel::ReJSONKeyModel(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray fullPath,\n    int dbIndex, long long ttl)\n    : KeyModel(connection, fullPath, dbIndex, ttl) {}\n\nQString ReJSONKeyModel::type() { return \"ReJSON\"; }\n\nQStringList ReJSONKeyModel::getColumnNames() {\n  return QStringList() << \"value\";\n}\n\nQHash<int, QByteArray> ReJSONKeyModel::getRoles() {\n  QHash<int, QByteArray> roles;\n  roles[Roles::Value] = \"value\";\n  return roles;\n}\n\nQVariant ReJSONKeyModel::getData(int rowIndex, int dataRole) {\n  if (!isRowLoaded(rowIndex)) return QVariant();\n\n  if (dataRole == Roles::Value) return m_rowsCache[rowIndex];\n\n  return QVariant();\n}\n\nvoid ReJSONKeyModel::updateRow(int rowIndex, const QVariantMap& row,\n                               Callback c) {\n  if (rowIndex > 0 || !isRowValid(row)) {\n    qDebug() << \"Row is not valid\";\n    return;\n  }\n\n  QByteArray value = row.value(\"value\").toByteArray();\n\n  if (value.isEmpty()) return;\n\n  auto responseHandler = [this, value](RedisClient::Response r, Callback c) {\n    if (r.isOkMessage()) {\n      m_rowsCache.clear();\n      m_rowsCache.addLoadedRange({0, 0}, (QList<QByteArray>() << value));\n      return c(QString());\n    } else {\n      return c(r.value().toString());\n    }\n  };\n\n  executeCmd({\"JSON.SET\", m_keyFullPath, \".\", value}, c, responseHandler);\n}\n\nvoid ReJSONKeyModel::addRow(const QVariantMap& row, Callback c) {\n  updateRow(0, row, c);\n}\n\nvoid ReJSONKeyModel::loadRows(QVariant, unsigned long,\n                              LoadRowsCallback callback) {\n  auto onConnectionError = [callback](const QString& err) {\n    return callback(err, 0);\n  };\n\n  auto responseHandler = [this, callback](RedisClient::Response r, Callback) {\n    m_rowsCache.clear();\n    m_rowsCache.push_back(r.value().toByteArray());\n\n    callback(QString(), 1);\n  };\n\n  executeCmd({\"JSON.GET\", m_keyFullPath, \"noescape\"}, onConnectionError, responseHandler,\n             RedisClient::Response::String);\n}\n\nvoid ReJSONKeyModel::removeRow(int, Callback) {\n  m_rowCount--;\n  setRemovedIfEmpty();\n}\n"
  },
  {
    "path": "src/app/models/key-models/rejsonkey.h",
    "content": "#pragma once\n#include \"abstractkey.h\"\n\nclass ReJSONKeyModel : public KeyModel<QByteArray> {\n public:\n  ReJSONKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                 QByteArray fullPath, int dbIndex, long long ttl);\n\n  QString type() override;\n  QStringList getColumnNames() override;\n  QHash<int, QByteArray> getRoles() override;\n  QVariant getData(int rowIndex, int dataRole) override;\n\n  void addRow(const QVariantMap&, ValueEditor::Model::Callback c) override;\n  virtual void updateRow(int rowIndex, const QVariantMap& row,\n                         ValueEditor::Model::Callback c) override;\n  void loadRows(QVariant, unsigned long, LoadRowsCallback callback) override;\n  void removeRow(int, ValueEditor::Model::Callback c) override;\n\n protected:\n  int addLoadedRowsToCache(const QVariantList&, QVariant) override { return 1; }\n\n private:\n  enum Roles { Value = Qt::UserRole + 1 };\n};\n"
  },
  {
    "path": "src/app/models/key-models/rowcache.h",
    "content": "#pragma once\n#include <QHash>\n#include <QList>\n#include <QPair>\n#include <exception>\n\ntypedef qlonglong RowIndex;\n\nclass CacheRange : public QPair<RowIndex, RowIndex> {\n public:\n  CacheRange(const RowIndex& f = -1, const RowIndex& s = -1)\n      : QPair<RowIndex, RowIndex>(f, s) {}\n\n  bool isEmpty() const { return first == -1 && second == -1; }\n};\n\ntemplate <typename T>\nclass MappedCache {\n public:\n  MappedCache() : m_valid(false) {}\n\n  bool isValid() const { return m_valid; }\n\n  void addLoadedRange(const CacheRange& range, const QList<T>& dataForRange) {\n    if (!isValid()) clear();\n\n    m_mapping[range] = dataForRange;\n  }\n\n  bool isRowLoaded(RowIndex index) {\n    CacheRange i = findTargetRange(index);\n    return !i.isEmpty();\n  }\n\n  T getRow(RowIndex index) {\n    if (!isRowLoaded(index)) return T();\n\n    CacheRange i = findTargetRange(index);\n    return m_mapping[i].at(index - i.first);\n  }\n\n  T operator[](RowIndex index) { return getRow(index); }\n\n  void replace(RowIndex index, T row) {\n    if (!isRowLoaded(index)) {\n      throw std::out_of_range(\"Invalid row\");\n    }\n    CacheRange i = findTargetRange(index);\n    return m_mapping[i].replace(index - i.first, row);\n  }\n\n  void removeAt(RowIndex index) {\n    if (!isRowLoaded(index)) {\n      throw std::out_of_range(\"Invalid row\");\n    }\n    CacheRange i = findTargetRange(index);\n\n    m_mapping[i].removeAt(index - i.first);\n    CacheRange newKey{i.first, i.second - 1};\n    replaceRangeInMapping(newKey, i);\n    m_valid = false;\n  }\n\n  void push_back(const T& row) {\n    CacheRange newKey{0, 1};\n\n    if (m_mapping.size() > 0) {\n      newKey.first += m_mapping.lastKey().first;\n      newKey.second += m_mapping.lastKey().second;\n\n      m_mapping.last().push_back(row);\n      replaceRangeInMapping(newKey);\n    } else {\n      m_mapping.insert(newKey, QList<T>{row});\n    }\n  }\n\n  unsigned long long size() const {\n    unsigned long long cacheSize = 0;\n    for (auto cachePage : m_mapping) {\n      cacheSize += cachePage.size();\n    }\n    return cacheSize;\n  }\n\n  void clear() {\n    m_mapping.clear();\n    m_valid = true;\n  }\n\n private:\n  CacheRange findTargetRange(RowIndex index) {\n    for (auto i = m_mapping.constBegin(); i != m_mapping.constEnd(); ++i) {\n      if (i.key().first <= index && index <= i.key().second) {\n        return i.key();\n      }\n    }\n    return CacheRange();\n  }\n\n  void replaceRangeInMapping(const CacheRange& newRange,\n                             const CacheRange& current = CacheRange()) {\n    CacheRange replaceKey = current.isEmpty() ? m_mapping.lastKey() : current;\n\n    m_mapping[newRange] = m_mapping[replaceKey];\n    m_mapping.remove(replaceKey);\n  }\n\n private:\n  QMap<CacheRange, QList<T>> m_mapping;\n  bool m_valid;\n};\n"
  },
  {
    "path": "src/app/models/key-models/setkey.cpp",
    "content": "#include \"setkey.h\"\n#include <qredisclient/connection.h>\n\nSetKeyModel::SetKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                         QByteArray fullPath, int dbIndex, long long ttl)\n    : ListLikeKeyModel(connection, fullPath, dbIndex, ttl, \"SCARD\", \"SSCAN\") {}\n\nQString SetKeyModel::type() { return \"set\"; }\n\nvoid SetKeyModel::updateRow(int rowIndex, const QVariantMap &row, Callback c) {\n  if (!isRowLoaded(rowIndex) || !isRowValid(row)) {\n    emit m_notifier->error(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n    return;\n  }\n\n  QByteArray cachedRow = m_rowsCache[rowIndex];\n  QByteArray newRow(row[\"value\"].toByteArray());\n\n  auto onRowAdded = [this, c, rowIndex, newRow](const QString &err) {\n    if (err.isEmpty()) m_rowsCache.replace(rowIndex, newRow);\n    return c(err);\n  };\n\n  deleteSetRow(cachedRow, [this, c, newRow, onRowAdded](const QString &err) {\n    if (err.size() > 0) return c(err);\n\n    addSetRow(newRow, onRowAdded);\n  });\n}\n\nvoid SetKeyModel::addRow(const QVariantMap &row, Callback c) {\n  if (!isRowValid(row)) {\n    return c(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n  }\n\n  addSetRow(row[\"value\"].toByteArray(), [this, c](const QString &err) {\n    if (err.isEmpty()) {\n      m_rowCount++;\n    }\n\n    return c(err);\n  });\n}\n\nvoid SetKeyModel::removeRow(int i, Callback c) {\n  if (!isRowLoaded(i)) return;\n\n  QByteArray value = m_rowsCache[i];\n  deleteSetRow(value, [this, c, i](const QString &err) {\n    if (err.isEmpty()) {\n      m_rowCount--;\n      m_rowsCache.removeAt(i);\n\n      setRemovedIfEmpty();\n    }\n\n    return c(err);\n  });\n}\n\nvoid SetKeyModel::addSetRow(const QByteArray &value, Callback c) {\n  executeCmd({\"SADD\", m_keyFullPath, value}, c);\n}\n\nvoid SetKeyModel::deleteSetRow(const QByteArray &value, Callback c) {\n  executeCmd({\"SREM\", m_keyFullPath, value}, c);\n}\n"
  },
  {
    "path": "src/app/models/key-models/setkey.h",
    "content": "#pragma once\n#include \"listlikekey.h\"\n\nclass SetKeyModel : public ListLikeKeyModel {\n public:\n  SetKeyModel(QSharedPointer<RedisClient::Connection> connection,\n              QByteArray fullPath, int dbIndex, long long ttl);\n\n  QString type() override;\n\n  void addRow(const QVariantMap &, Callback c) override;\n  virtual void updateRow(int rowIndex, const QVariantMap &,\n                         Callback c) override;\n  void removeRow(int, Callback c) override;\n\n private:\n  void addSetRow(const QByteArray &value, Callback c);\n  void deleteSetRow(const QByteArray &value, Callback c);\n};\n"
  },
  {
    "path": "src/app/models/key-models/sortedsetkey.cpp",
    "content": "#include \"sortedsetkey.h\"\n#include <qredisclient/connection.h>\n\nSortedSetKeyModel::SortedSetKeyModel(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray fullPath,\n    int dbIndex, long long ttl)\n    : KeyModel(connection, fullPath, dbIndex, ttl, \"ZCARD\",\n               \"ZRANGE WITHSCORES\") {}\n\nQString SortedSetKeyModel::type() { return \"zset\"; }\n\nQStringList SortedSetKeyModel::getColumnNames() {\n  return QStringList() << \"rowNumber\"\n                       << \"value\"\n                       << \"score\";\n}\n\nQHash<int, QByteArray> SortedSetKeyModel::getRoles() {\n  QHash<int, QByteArray> roles;\n  roles[Roles::RowNumber] = \"rowNumber\";\n  roles[Roles::Value] = \"value\";\n  roles[Roles::Score] = \"score\";\n  return roles;\n}\n\nQVariant SortedSetKeyModel::getData(int rowIndex, int dataRole) {\n  if (!isRowLoaded(rowIndex)) return QVariant();\n\n  QPair<QByteArray, QByteArray> row = m_rowsCache[rowIndex];\n\n  if (dataRole == Roles::Value)\n    return row.first;\n  else if (dataRole == Roles::Score)\n    return row.second.toDouble();\n  else if (dataRole == Roles::RowNumber)\n    return rowIndex;\n\n  return QVariant();\n}\n\nvoid SortedSetKeyModel::updateRow(int rowIndex, const QVariantMap &row,\n                                  Callback c) {\n  if (!isRowLoaded(rowIndex) || !isRowValid(row)) {\n    emit m_notifier->error(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n    return;\n  }\n\n  QPair<QByteArray, QByteArray> cachedRow = m_rowsCache[rowIndex];\n\n  bool valueChanged = cachedRow.first != row[\"value\"].toByteArray();\n  bool scoreChanged = cachedRow.second != row[\"score\"].toByteArray();\n\n  QPair<QByteArray, QByteArray> newRow(\n      (valueChanged) ? row[\"value\"].toByteArray() : cachedRow.first,\n      (scoreChanged) ? row[\"score\"].toByteArray() : cachedRow.second);\n\n  auto onRowAdded = [this, c, rowIndex, newRow](const QString &err) {\n    if (err.isEmpty()) m_rowsCache.replace(rowIndex, newRow);\n\n    return c(err);\n  };\n\n  if (valueChanged) {\n    deleteSortedSetRow(\n        cachedRow.first, [this, c, onRowAdded, newRow](const QString &err) {\n          if (err.size() > 0) return c(err);\n\n          addSortedSetRow(newRow.first, newRow.second, onRowAdded, false);\n        });\n  } else {\n    addSortedSetRow(newRow.first, newRow.second, onRowAdded, true);\n  }\n}\n\nvoid SortedSetKeyModel::addRow(const QVariantMap &row, Callback c) {\n  if (!isRowValid(row)) {\n    return c(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n  }\n\n  auto onAdded = [this, c](const QString &err) {\n    if (err.isEmpty()) m_rowCount++;\n\n    return c(err);\n  };\n\n  addSortedSetRow(row[\"value\"].toByteArray(), row[\"score\"].toByteArray(),\n                  onAdded);\n}\n\nvoid SortedSetKeyModel::removeRow(int i, Callback c) {\n  if (!isRowLoaded(i)) return;\n\n  QByteArray value = m_rowsCache[i].first;\n\n  executeCmd({\"ZREM\", m_keyFullPath, value}, [this, c, i](const QString &err) {\n    if (err.isEmpty()) {\n      m_rowCount--;\n      m_rowsCache.removeAt(i);\n      setRemovedIfEmpty();\n    }\n\n    return c(err);\n  });\n}\n\nvoid SortedSetKeyModel::addSortedSetRow(const QByteArray &value,\n                                        QByteArray score, Callback c,\n                                        bool updateExisting) {\n  QList<QByteArray> cmd;\n\n  if (updateExisting) {\n    cmd = {\"ZADD\", m_keyFullPath, \"XX\", score, value};\n  } else {\n    cmd = {\"ZADD\", m_keyFullPath, score, value};\n  }\n\n  executeCmd(cmd, c, CmdHandler(), RedisClient::Response::Integer);\n}\n\nvoid SortedSetKeyModel::deleteSortedSetRow(const QByteArray &value,\n                                           Callback c) {\n  executeCmd({\"ZREM\", m_keyFullPath, value}, c);\n}\n\nint SortedSetKeyModel::addLoadedRowsToCache(const QVariantList &rows,\n                                            QVariant rowStartId) {\n  QList<QPair<QByteArray, QByteArray>> result;\n\n  for (QVariantList::const_iterator item = rows.begin(); item != rows.end();\n       ++item) {\n    QPair<QByteArray, QByteArray> value;\n    value.first = item->toByteArray();\n    ++item;\n\n    if (item == rows.end()) {\n      emit m_notifier->error(QCoreApplication::translate(\n          \"RESP\", \"Data was loaded from server partially.\"));\n      return 0;\n    }\n\n    value.second = item->toByteArray();\n    result.push_back(value);\n  }\n\n  auto rowStart = rowStartId.toLongLong();\n  m_rowsCache.addLoadedRange({rowStart, rowStart + result.size() - 1}, result);\n\n  return result.size();\n}\n"
  },
  {
    "path": "src/app/models/key-models/sortedsetkey.h",
    "content": "#pragma once\n#include \"abstractkey.h\"\n\nclass SortedSetKeyModel : public KeyModel<QPair<QByteArray, QByteArray>> {\n public:\n  SortedSetKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                    QByteArray fullPath, int dbIndex, long long ttl);\n\n  QString type() override;\n  QStringList getColumnNames() override;\n  QHash<int, QByteArray> getRoles() override;\n  QVariant getData(int rowIndex, int dataRole) override;\n\n  void addRow(const QVariantMap&, Callback c) override;\n  virtual void updateRow(int rowIndex, const QVariantMap&, Callback c) override;\n  void removeRow(int, Callback c) override;\n\n protected:\n  int addLoadedRowsToCache(const QVariantList& list,\n                           QVariant rowStart) override;\n\n private:\n  enum Roles { RowNumber = Qt::UserRole + 1, Value, Score };\n\n  void addSortedSetRow(const QByteArray& value, QByteArray score, Callback c,\n                       bool updateExisting = false);\n  void deleteSortedSetRow(const QByteArray& value, Callback c);\n};\n"
  },
  {
    "path": "src/app/models/key-models/stream.cpp",
    "content": "#include \"stream.h\"\n#include <QJsonDocument>\n#include <QJsonArray>\n#include <QJsonObject>\n#include <QJsonValue>\n\n#include \"app/jsonutils.h\"\n\nStreamKeyModel::StreamKeyModel(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray fullPath,\n    int dbIndex, long long ttl)\n    : KeyModel(connection, fullPath, dbIndex, ttl, \"XLEN\", QByteArray()) {}\n\nQString StreamKeyModel::type() { return \"stream\"; }\n\nQStringList StreamKeyModel::getColumnNames() {\n  return QStringList() << \"rowNumber\"\n                       << \"id\"\n                       << \"value\";\n}\n\nQHash<int, QByteArray> StreamKeyModel::getRoles() {\n  QHash<int, QByteArray> roles;\n  roles[Roles::RowNumber] = \"rowNumber\";\n  roles[Roles::ID] = \"id\";\n  roles[Roles::Value] = \"value\";\n  return roles;\n}\n\nQVariant StreamKeyModel::getData(int rowIndex, int dataRole) {\n  if (!isRowLoaded(rowIndex)) return QVariant();\n  switch (dataRole) {\n    case Value:\n      return QJsonDocument::fromVariant(m_rowsCache[rowIndex].second)\n          .toJson(QJsonDocument::Compact);\n    case ID:\n      return m_rowsCache[rowIndex].first;\n    case RowNumber:\n      return rowIndex;\n  }\n\n  return QVariant();\n}\n\nvoid StreamKeyModel::addRow(const QVariantMap &row,\n                            ValueEditor::Model::Callback c) {\n  if (!isRowValid(row)) {\n    c(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n    return;\n  }\n\n  QList<QByteArray> cmd = {\"XADD\", m_keyFullPath, row[\"id\"].toByteArray()};\n\n  QJsonParseError err;\n  QJsonDocument jsonValues =\n      QJsonDocument::fromJson(row[\"value\"].toByteArray(), &err);\n\n  if (err.error != QJsonParseError::NoError || !jsonValues.isObject()) {\n    return c(QCoreApplication::translate(\"RESP\", \"Invalid row\"));\n  }\n\n  auto valuesObject = jsonValues.object();\n\n  for (auto key : valuesObject.keys()) {\n    cmd.append(key.toUtf8());\n\n    if (valuesObject[key].isArray()) {\n        QJsonDocument d = QJsonDocument(valuesObject[key].toArray());\n        cmd.append(d.toJson(QJsonDocument::Compact));\n    } else if (valuesObject[key].isObject()) {\n        QJsonDocument d = QJsonDocument(valuesObject[key].toObject());\n        cmd.append(d.toJson(QJsonDocument::Compact));\n    } else {\n        cmd.append(valuesObject[key].toVariant().toByteArray());\n    }\n  }\n\n  executeCmd(cmd, c);\n}\n\nvoid StreamKeyModel::updateRow(int, const QVariantMap &,\n                               ValueEditor::Model::Callback) {\n    //NOTE(u_glide): Redis Streams doesn't support editing (yet?)\n}\n\nvoid StreamKeyModel::removeRow(int i, ValueEditor::Model::Callback c) {\n  if (!isRowLoaded(i)) return;\n\n  executeCmd({\"XDEL\", m_keyFullPath, m_rowsCache[i].first}, c);\n}\n\nvoid StreamKeyModel::loadRowsCount(ValueEditor::Model::Callback c)\n{\n  executeCmd(\n      {\"XINFO\", \"STREAM\", m_keyFullPath}, c,\n      [this](RedisClient::Response r, Callback c) {\n        auto info = r.value().toList();\n        auto it = info.begin();\n\n        while (it != info.end()) {\n          if (!it->canConvert(QMetaType::QByteArray)) {\n            continue;\n          }\n\n          QByteArray propertyName = it->toByteArray();\n\n          it++;\n\n          if (it == info.end())\n              break;\n\n          if (propertyName == QByteArray(\"length\")) {\n            m_rowCount = it->toLongLong();\n          } else if (propertyName == QByteArray(\"first-entry\") ||\n                     propertyName == QByteArray(\"last-entry\")) {\n            auto list = it->toList();\n\n            if (list.size() > 0) {\n              m_filters[QString::fromLatin1(propertyName)] = list[0];\n            }\n          }\n\n          it++;\n        }\n\n        c(QString());\n      },\n      RedisClient::Response::Type::Array);\n}\n\nint StreamKeyModel::addLoadedRowsToCache(const QVariantList &rows,\n                                         QVariant rowStartId) {\n  QList<QPair<QByteArray, QVariant>> result;\n\n  for (QVariantList::const_iterator item = rows.begin(); item != rows.end();\n       ++item) {\n    QPair<QByteArray, QVariant> value;\n    auto rowValues = item->toList();\n    value.first = rowValues[0].toByteArray();\n\n    QVariantList valuesList = rowValues[1].toList();\n    QVariantMap mappedVal;\n\n    for (QVariantList::const_iterator valItem = valuesList.begin();\n         valItem != valuesList.end(); ++valItem) {               \n      auto valKey = valItem->toByteArray();\n      valItem++;\n\n      QByteArray fieldValue = valItem->toByteArray();\n\n      if (JSONUtils::isJSON(fieldValue)) {\n        auto doc = QJsonDocument::fromJson(fieldValue);\n\n        if (doc.isObject() || doc.isArray()) {\n            mappedVal[valKey] = doc.toVariant();\n        } else {\n            mappedVal[valKey] = fieldValue;\n        }\n      } else {\n          mappedVal[valKey] = fieldValue;\n      }\n    }\n\n    value.second = mappedVal;\n    result.push_back(value);\n  }\n\n  auto rowStart = rowStartId.toLongLong();\n  m_rowsCache.addLoadedRange({rowStart, rowStart + result.size() - 1}, result);\n\n  return result.size();\n}\n\nQList<QByteArray> StreamKeyModel::getRangeCmd(QVariant rowStartId,\n                                              unsigned long count) {\n  QList<QByteArray> cmd;\n  cmd << \"XREVRANGE\" << m_keyFullPath;\n\n  if (filter(\"end\").isNull()) {  // end\n    cmd << \"+\";\n  } else {\n    cmd << QString::number(filter(\"end\").toLongLong()).toLatin1();\n  }\n\n  if (filter(\"start\").isNull()) {  // start\n    unsigned long rowStart = rowStartId.toULongLong();\n\n    if (isRowLoaded(rowStart - 1)) {\n      cmd << m_rowsCache[rowStart - 1].first;\n    } else {\n       cmd << \"-\";\n    }\n  } else {\n    cmd << QString::number(filter(\"start\").toLongLong()).toLatin1();\n  }\n\n  return cmd << \"COUNT\" << QString::number(count).toLatin1();\n}\n"
  },
  {
    "path": "src/app/models/key-models/stream.h",
    "content": "#pragma once\n#include \"abstractkey.h\"\n\nclass StreamKeyModel : public KeyModel<QPair<QByteArray, QVariant>> {\n public:\n  StreamKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                 QByteArray fullPath, int dbIndex, long long ttl);\n\n  QString type() override;\n  QStringList getColumnNames() override;\n  QHash<int, QByteArray> getRoles() override;\n\n  QVariant getData(int rowIndex, int dataRole) override;\n\n  void addRow(const QVariantMap &, Callback) override;\n\n  virtual void updateRow(int rowIndex, const QVariantMap &, Callback) override;\n\n  void removeRow(int, Callback) override;\n\n   void loadRowsCount(ValueEditor::Model::Callback c) override;\n\n protected:\n  int addLoadedRowsToCache(const QVariantList &list,\n                           QVariant rowStart) override;\n  virtual QList<QByteArray> getRangeCmd(QVariant rowStartId,\n                                        unsigned long count) override;\n\n protected:\n  enum Roles { RowNumber = Qt::UserRole + 1, ID, Value };\n};\n"
  },
  {
    "path": "src/app/models/key-models/stringkey.cpp",
    "content": "#include \"stringkey.h\"\n#include <qredisclient/connection.h>\n\nStringKeyModel::StringKeyModel(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray fullPath,\n    int dbIndex, long long ttl)\n    : KeyModel(connection, fullPath, dbIndex, ttl), m_type(\"string\") {}\n\nQString StringKeyModel::type() { return m_type; }\n\nQStringList StringKeyModel::getColumnNames() {\n  return QStringList() << \"value\";\n}\n\nQHash<int, QByteArray> StringKeyModel::getRoles() {\n  QHash<int, QByteArray> roles;\n  roles[Roles::Value] = \"value\";\n  return roles;\n}\n\nQVariant StringKeyModel::getData(int rowIndex, int dataRole) {\n  if (rowIndex > 0 || !isRowLoaded(rowIndex)) return QVariant();\n  if (dataRole == Roles::Value) return m_rowsCache[rowIndex];\n\n  return QVariant();\n}\n\nvoid StringKeyModel::updateRow(int rowIndex, const QVariantMap& row,\n                               Callback c) {\n  if (rowIndex > 0 || !isRowValid(row)) {\n    qDebug() << \"Row is not valid\";\n    return;\n  }\n\n  QByteArray value = row.value(\"value\").toByteArray();\n\n  executeCmd(\n      {\"SET\", m_keyFullPath, value}, [this, c, value](const QString& err) {\n        if (err.isEmpty()) {\n          m_rowsCache.clear();\n          m_rowsCache.addLoadedRange({0, 0}, (QList<QByteArray>() << value));\n        }\n\n        return c(err);\n      });\n}\n\nvoid StringKeyModel::addRow(const QVariantMap& row, Callback c) {\n  if (m_type == \"hyperloglog\") {\n      QByteArray value = row.value(\"value\").toByteArray();\n\n      executeCmd(\n          {\"PFADD\", m_keyFullPath, value}, [this, c](const QString& err) {\n            m_rowCount++;\n            return c(err);\n          });\n  } else {\n    updateRow(0, row, c);\n  }\n}\n\nvoid StringKeyModel::loadRows(QVariant, unsigned long,\n                              LoadRowsCallback callback) {\n  auto onConnectionError = [callback](const QString& err) {\n    return callback(err, 0);\n  };\n\n  auto responseHandler = [this, callback](RedisClient::Response r, Callback) {\n    m_rowsCache.clear();\n\n    QByteArray value = r.value().toByteArray();\n\n    m_rowsCache.push_back(value);\n    m_rowCount = 1;\n\n    // Detect HyperLogLog\n    if (value.startsWith(\"HYLL\")) {\n      executeCmd(\n          {\"PFCOUNT\", m_keyFullPath}, [callback](const QString&) { callback(QString(), 1); },\n          [this, callback](RedisClient::Response r, Callback) {\n            m_type = \"hyperloglog\";\n            m_rowCount = r.value().toUInt();\n            callback(QString(), m_rowCount);\n          },\n          RedisClient::Response::Integer);\n    } else {\n      callback(QString(), 1);\n    }\n  };\n\n  executeCmd({\"GET\", m_keyFullPath}, onConnectionError, responseHandler,\n             RedisClient::Response::String);\n}\n\nvoid StringKeyModel::removeRow(int, Callback) {\n  m_rowCount--;\n  setRemovedIfEmpty();\n}\n"
  },
  {
    "path": "src/app/models/key-models/stringkey.h",
    "content": "#pragma once\n#include \"abstractkey.h\"\n\nclass StringKeyModel : public KeyModel<QByteArray> {\n public:\n  StringKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                 QByteArray fullPath, int dbIndex, long long ttl);\n\n  QString type() override;\n  QStringList getColumnNames() override;\n  QHash<int, QByteArray> getRoles() override;\n  QVariant getData(int rowIndex, int dataRole) override;\n\n  void addRow(const QVariantMap&, Callback c) override;\n  virtual void updateRow(int rowIndex, const QVariantMap& row,\n                         Callback c) override;\n  void loadRows(QVariant, unsigned long, LoadRowsCallback callback) override;\n  void removeRow(int, Callback c) override;\n\n  virtual unsigned long rowsCount() override {\n      return m_rowCount;\n  }\n\n protected:\n  int addLoadedRowsToCache(const QVariantList&, QVariant) override { return 1; }\n\n private:\n  enum Roles { Value = Qt::UserRole + 1 };\n\n  QString m_type;\n};\n"
  },
  {
    "path": "src/app/models/key-models/unknownkey.cpp",
    "content": "#include \"unknownkey.h\"\n#include <qredisclient/connection.h>\n\nUnknownKeyModel::UnknownKeyModel(\n    QSharedPointer<RedisClient::Connection> connection, QByteArray fullPath,\n    int dbIndex, long long ttl, QString type)\n    : KeyModel(connection, fullPath, dbIndex, ttl), m_type(type) {}\n"
  },
  {
    "path": "src/app/models/key-models/unknownkey.h",
    "content": "#pragma once\n#include \"abstractkey.h\"\n\n#define EMPTY_METHOD override {qWarning() << \"Operation is not supported\";}\n\nclass UnknownKeyModel : public KeyModel<QByteArray> {\n public:\n  UnknownKeyModel(QSharedPointer<RedisClient::Connection> connection,\n                 QByteArray fullPath, int dbIndex, long long ttl, QString type);\n\n  QString type() override { return m_type; }\n\n  QStringList getColumnNames() override {\n      return QStringList() << \"\";\n  }\n  QHash<int, QByteArray> getRoles() override {\n    return QHash<int, QByteArray>();\n  }\n\n  QVariant getData(int, int) override {return QVariant();}\n\n  void addRow(const QVariantMap&, Callback) EMPTY_METHOD\n\n  virtual void updateRow(int, const QVariantMap&,\n                         Callback) EMPTY_METHOD\n\n  void loadRows(QVariant, unsigned long, LoadRowsCallback callback) override {\n      return callback(\"unknown-data-type\", 0);\n  }\n\n  void removeRow(int, Callback) EMPTY_METHOD\n\n  virtual unsigned long rowsCount() override {\n      return 0;\n  }\n\n protected:\n  int addLoadedRowsToCache(const QVariantList&, QVariant) override { return 1; }\n\n private:\n  enum Roles { Value = Qt::UserRole + 1 };\n\n  QString m_type;\n};\n"
  },
  {
    "path": "src/app/models/treeoperations.cpp",
    "content": "#include \"treeoperations.h\"\n\n#include <asyncfuture.h>\n#include <qredisclient/redisclient.h>\n#include <QRegExp>\n#include <QRegularExpression>\n#include <QRegularExpressionMatchIterator>\n#include <QSet>\n#include <QtConcurrent>\n#include <algorithm>\n\n#include \"app/events.h\"\n#include \"connections-tree/items/serveritem.h\"\n#include \"connections-tree/items/databaseitem.h\"\n#include \"connections-tree/items/namespaceitem.h\"\n#include \"connections-tree/keysrendering.h\"\n\nTreeOperations::TreeOperations(const ServerConfig &config,\n    QSharedPointer<Events> events)\n    : m_events(events), m_dbCount(0),\n      m_connectionMode(RedisClient::Connection::Mode::Normal),\n      m_config(config){\n  m_connection = QSharedPointer<RedisClient::Connection>(\n              new RedisClient::Connection(config));\n  m_events->registerLoggerForConnection(*m_connection);\n}\n\nvoid TreeOperations::loadDatabases(\n    QSharedPointer<RedisClient::Connection> c,\n    QSharedPointer<AsyncFuture::Deferred<void>> d,\n    std::function<void(RedisClient::DatabaseList, const QString&)> callback) {\n  if (!d) return;\n\n  auto connection = c->clone(false);\n  auto connectionWRef = connection.toWeakRef();\n\n  d->onCanceled([connectionWRef]() {\n    QtConcurrent::run([connectionWRef]() {\n      auto connection = connectionWRef.toStrongRef();\n      if (connection) connection->disconnect();\n    });\n  });\n\n  m_events->registerLoggerForConnection(*connection);\n\n  if (!connect(connection)) {\n    return callback(RedisClient::DatabaseList(),\n                    QString(\"Cannot connect to redis-server\"));\n  }\n\n  if (d && d->future().isCanceled()) {\n    return;\n  }\n\n  RedisClient::DatabaseList availableDatabeses = connection->getKeyspaceInfo();\n\n  if (connection->mode() == RedisClient::Connection::Mode::Cluster) {\n    return callback(availableDatabeses, QString());\n  }\n\n  // detect all databases\n  RedisClient::Response scanningResp;\n  int lastDbIndex =\n      (availableDatabeses.size() == 0) ? 0 : availableDatabeses.lastKey() + 1;\n\n  if (m_dbCount > 0) {\n    for (int index = lastDbIndex; index < m_dbCount; index++) {\n      availableDatabeses.insert(index, 0);\n    }\n\n    return callback(availableDatabeses, QString());\n  } else {\n    m_dbCount = lastDbIndex;\n\n    auto collectedDatabases = QSharedPointer<RedisClient::DatabaseList>(\n        new RedisClient::DatabaseList(availableDatabeses));\n\n    recursiveSelectScan(d, connection, collectedDatabases, callback);\n  }\n}\n\nvoid TreeOperations::recursiveSelectScan(\n    QSharedPointer<AsyncFuture::Deferred<void>> d,\n    QSharedPointer<RedisClient::Connection> c,\n    QSharedPointer<RedisClient::DatabaseList> dbList,\n    std::function<void(RedisClient::DatabaseList, const QString&)> callback) {\n  if (d && d->future().isCanceled()) {\n    return;\n  }\n\n  if (m_dbCount >= m_config.databaseScanLimit() || !c) {\n    return callback(*dbList, QString());\n  }\n\n  auto errHandler = [callback, dbList](const QString& err) {\n    if (dbList && dbList->size() > 0) {\n      callback(*dbList, QString());\n    } else {\n      callback(RedisClient::DatabaseList(), err);\n    }\n  };\n\n  c->cmd(\n      {\"select\", QString::number(m_dbCount).toLatin1()}, this, -1,\n      [this, dbList, c, callback, d](const RedisClient::Response& scanningResp) {\n        if (d && d->future().isCanceled()) {\n          return;\n        }\n\n        if (!scanningResp.isOkMessage()) {\n          callback(*dbList, QString());\n          return;\n        }\n\n        dbList->insert(m_dbCount, 0);\n        m_dbCount++;\n\n        recursiveSelectScan(d, c, dbList, callback);\n      },\n      errHandler);\n}\n\nbool TreeOperations::connect(QSharedPointer<RedisClient::Connection> c) {\n  if (c->isConnected()) return true;\n\n  try {\n    if (!c->connect(true)) {\n      emit m_events->error(\n          QCoreApplication::translate(\n              \"RESP\", \"Cannot connect to server '%1'. Check log for details.\")\n              .arg(m_connection->getConfig().name()));\n      return false;\n    }\n\n    m_connectionMode = c->mode();\n    return true;\n  } catch (const RedisClient::Connection::SSHSupportException& e) {\n      emit m_events->error(\n          QCoreApplication::translate(\"RESP\", \"Open Source version of RESP.app <b>doesn't support SSH tunneling</b>.<br /><br /> \"\n                                             \"To get fully-featured application, please buy subscription on \"\n                                             \"<a href='https://resp.app/subscriptions'>resp.app</a>. <br/><br />\"\n                                             \"Every single subscription gives us funds to continue \"\n                                             \"the development process and provide support to our users. <br />\"\n                                             \"If you have any questions please feel free to contact us \"\n                                             \"at <a href='mailto:support@resp.app'>support@resp.app</a> \"\n                                             \"or join <a href='https://t.me/RedisDesktopManager'>Telegram chat</a>.\")\n      );\n      return false;\n  } catch (const RedisClient::Connection::Exception& e) {\n    emit m_events->error(\n        QCoreApplication::translate(\"RESP\", \"Connection error: \") +\n        QString(e.what()));\n    return false;\n  }\n}\n\nvoid TreeOperations::requestBulkOperation(\n    ConnectionsTree::AbstractNamespaceItem& ns,\n    BulkOperations::Manager::Operation op,\n    BulkOperations::AbstractOperation::OperationCallback callback) {\n  QString pattern =\n      QString(\"%1%2*\")\n          .arg(QString::fromUtf8(ns.getFullPath()))\n          .arg(ns.getFullPath().size() > 0 ? m_config.namespaceSeparator()\n                                           : \"\");\n  QRegExp filter(pattern, Qt::CaseSensitive, QRegExp::Wildcard);\n\n  auto dbIndex = ns.getDbIndex();\n\n  getReadyConnection([this, dbIndex, filter, op,\n                      callback](QSharedPointer<RedisClient::Connection> c) {\n    // NOTE(u_glide): Use \"clean\" connection wihout logger here for better\n    // performance\n    emit m_events->requestBulkOperation(c->clone(), dbIndex, op, filter,\n                                        callback);\n  });\n}\n\nvoid TreeOperations::getReadyConnection(TreeOperations::PendingOperation callback)\n{\n    if (m_config.askForSshPassword() && m_config.sshPassword().isEmpty()) {\n        m_pendingOperation = callback;\n        m_config.setOwner(sharedFromThis().toWeakRef());\n        emit secretRequired(m_config, ServerConfig::SSH_SECRET_ID);\n    } else {\n        return callback(m_connection);\n    }\n}\n\nQFuture<void> TreeOperations::getDatabases(\n    QSharedPointer<GetDatabasesCallback> callback) {\n  m_dbScanOp = QSharedPointer<AsyncFuture::Deferred<void>>(\n      new AsyncFuture::Deferred<void>());\n\n  getReadyConnection(\n      [this, callback](QSharedPointer<RedisClient::Connection> c) {\n        QtConcurrent::run(this, &TreeOperations::loadDatabases, c, m_dbScanOp,\n                          [callback](RedisClient::DatabaseList dbs, const QString& err){\n            callback->call(dbs, err);\n        });\n      });\n\n  return m_dbScanOp->future();\n}\n\nvoid TreeOperations::loadNamespaceItems(\n    uint dbIndex, const QString& filter,\n    QSharedPointer<LoadNamespaceItemsCallback> callback) {\n  QString keyPattern = filter.isEmpty() ? m_config.keysPattern() : filter;\n\n  if (m_filterHistory.contains(keyPattern)) {\n    m_filterHistory[keyPattern] = m_filterHistory[keyPattern].toInt() + 1;\n  } else {\n    m_filterHistory[keyPattern] = 1;\n  }\n  m_config.setFilterHistory(m_filterHistory);\n  emit filterHistoryUpdated();\n\n  QSettings settings;\n  qlonglong scanLimit = settings.value(\"app/scanLimit\", DEFAULT_SCAN_LIMIT).toLongLong();\n\n  getReadyConnection([this, dbIndex, filter, callback,\n                      keyPattern, scanLimit](QSharedPointer<RedisClient::Connection> c) {\n    if (!connect(c)) return;\n\n    auto processErr = [callback](const QString& err) {\n      return callback->call(\n          RedisClient::Connection::RawKeysList(),\n          QCoreApplication::translate(\"RESP\", \"Cannot load keys: %1\").arg(err));\n    };\n\n    auto callbackWrapper = [callback](const RedisClient::Connection::RawKeysList &keys,\n        const QString &err) {\n      return callback->call(keys, err);\n    };\n\n    try {\n      if (m_connection->mode() == RedisClient::Connection::Mode::Cluster) {\n        m_connection->getClusterKeys(callbackWrapper, keyPattern, scanLimit);\n      } else {\n        m_connection->cmd(\n            {\"ping\"}, this, dbIndex,\n            [this, callbackWrapper, keyPattern,\n             processErr, scanLimit](const RedisClient::Response& r) {\n              if (r.isErrorMessage()) {\n                return processErr(r.value().toString());\n              }\n              m_connection->getDatabaseKeys(callbackWrapper, keyPattern, -1, scanLimit);\n            },\n            [processErr](const QString& err) { return processErr(err); });\n      }\n\n    } catch (const RedisClient::Connection::Exception& error) {\n      processErr(error.what());\n    }\n  });\n}\n\nvoid TreeOperations::disconnect() { m_connection->disconnect(); }\n\nvoid TreeOperations::resetConnection() {\n  auto oldConnection = m_connection;\n  setConnection(oldConnection->clone());\n\n  QtConcurrent::run([oldConnection]() { oldConnection->disconnect(); });\n}\n\nQString TreeOperations::getNamespaceSeparator() {\n  return m_config.namespaceSeparator();\n}\n\nQString TreeOperations::defaultFilter() { return m_config.keysPattern(); }\n\nQVariantMap TreeOperations::getFilterHistory() {\n    m_filterHistory = m_config.filterHistory();\n    return m_filterHistory;\n}\n\nQString TreeOperations::connectionName() const {\n    return m_config.name();\n}\n\nvoid TreeOperations::openKeyTab(QSharedPointer<ConnectionsTree::KeyItem> key,\n                                bool openInNewTab) {\n  getReadyConnection(\n      [this, key, openInNewTab](QSharedPointer<RedisClient::Connection> c) {\n        emit m_events->openValueTab(c, key, openInNewTab);\n      });\n}\n\nvoid TreeOperations::openConsoleTab(int dbIndex) {\n  getReadyConnection(\n      [this, dbIndex](QSharedPointer<RedisClient::Connection> c) {\n        emit m_events->openConsole(c, dbIndex, true);\n      });\n}\n\nvoid TreeOperations::openNewKeyDialog(int dbIndex,\n                                      QSharedPointer<OpenNewKeyDialogCallback> callback,\n                                      QString keyPrefix) {\n  getReadyConnection([this, dbIndex, callback,\n                      keyPrefix](QSharedPointer<RedisClient::Connection> c) {\n    emit m_events->newKeyDialog(c, callback, dbIndex, keyPrefix);\n  });\n}\n\nvoid TreeOperations::openServerStats() {\n  getReadyConnection([this](QSharedPointer<RedisClient::Connection> c) {\n    emit m_events->openServerStats(c);\n  });\n}\n\nvoid TreeOperations::duplicateConnection() {\n  emit createNewConnection(m_config);\n}\n\nvoid TreeOperations::notifyDbWasUnloaded(int dbIndex) {\n  emit m_events->closeDbKeys(m_connection, dbIndex);\n}\n\nvoid TreeOperations::deleteDbKey(ConnectionsTree::KeyItem& key,\n                                 QSharedPointer<DeleteDbKeyCallback> callback) {\n  getReadyConnection(\n      [this, &key, callback](QSharedPointer<RedisClient::Connection> c) {\n        c->cmd(\n            {\"DEL\", key.getFullPath()}, this, key.getDbIndex(),\n            [this, &key](RedisClient::Response) {\n              key.setRemoved();\n              QRegExp filter(key.getFullPath(), Qt::CaseSensitive,\n                             QRegExp::Wildcard);\n              if (m_events)\n                m_events->closeDbKeys(m_connection, key.getDbIndex(), filter);\n            },\n            [this, callback](const QString& err) {\n              QString errorMsg =\n                  QCoreApplication::translate(\"RESP\", \"Delete key error: %1\")\n                      .arg(err);\n              callback->call(errorMsg);\n              if (m_events) m_events->error(errorMsg);\n            });\n      });\n}\n\nvoid TreeOperations::deleteDbKeys(ConnectionsTree::DatabaseItem& db) {\n  auto self = sharedFromThis().toWeakRef();\n  requestBulkOperation(\n      db, BulkOperations::Manager::Operation::DELETE_KEYS,\n      [self, this, &db](QRegExp filter, int, const QStringList&) {\n        if (!self) {\n          return;\n        }\n        uint dbIndex = db.getDbIndex();\n        db.reload();\n\n        if (m_events && m_connection) {\n          getReadyConnection(\n              [this, dbIndex, filter](QSharedPointer<RedisClient::Connection> c) {\n                emit m_events->closeDbKeys(c, dbIndex, filter);\n              });\n        }\n      });\n}\n\nvoid TreeOperations::deleteDbNamespace(ConnectionsTree::NamespaceItem& ns) {\n  auto self = sharedFromThis().toWeakRef();\n  requestBulkOperation(\n      ns, BulkOperations::Manager::Operation::DELETE_KEYS,\n      [this, self, &ns](QRegExp filter, int, const QStringList&) {\n        if (!self) {\n          return;\n        }\n        uint dbIndex = ns.getDbIndex();\n        ns.setRemoved();\n\n        if (m_events && m_connection) {\n          getReadyConnection(\n              [this, dbIndex, filter](QSharedPointer<RedisClient::Connection> c) {\n                emit m_events->closeDbKeys(c, dbIndex, filter);\n              });\n        }\n      });\n}\n\nvoid TreeOperations::setTTL(ConnectionsTree::AbstractNamespaceItem& ns) {\n  requestBulkOperation(ns, BulkOperations::Manager::Operation::TTL,\n                       [](QRegExp, int, const QStringList&) {});\n}\n\nvoid TreeOperations::copyKeys(ConnectionsTree::AbstractNamespaceItem& ns) {\n  requestBulkOperation(ns, BulkOperations::Manager::Operation::COPY_KEYS,\n                       [](QRegExp, int, const QStringList&) {});\n}\n\nvoid TreeOperations::importKeysFromRdb(ConnectionsTree::DatabaseItem& db) {\n  getReadyConnection([this, &db](QSharedPointer<RedisClient::Connection> c) {\n    emit m_events->requestBulkOperation(\n        c->clone(), db.getDbIndex(),\n        BulkOperations::Manager::Operation::IMPORT_RDB_KEYS, QRegExp(\".*\"),\n        [&db](QRegExp, int, const QStringList&) { db.reload(); });\n  });\n}\n\nvoid TreeOperations::flushDb(int dbIndex,\n                             QSharedPointer<FlushDbCallback> callback) {\n\n  auto callbackWrapper = [callback](const QString &err) {\n    callback->call(err);\n  };\n\n  getReadyConnection(\n      [dbIndex, callbackWrapper](QSharedPointer<RedisClient::Connection> c) {\n        try {\n          c->flushDbKeys(dbIndex, callbackWrapper);\n        } catch (const RedisClient::Connection::Exception& e) {\n          throw ConnectionsTree::Operations::Exception(\n              QCoreApplication::translate(\"RESP\", \"Cannot flush database: \") +\n              QString(e.what()));\n        }\n      });\n}\n\nQFuture<bool> TreeOperations::connectionSupportsMemoryOperations() {\n    return m_connection->isCommandSupported({\"MEMORY\", \"HELP\"});\n}\n\nvoid TreeOperations::openKeyIfExists(const QByteArray& fullPath,\n    QSharedPointer<ConnectionsTree::DatabaseItem> parent,\n    QSharedPointer<OpenKeyIfExistsCallback> callback) {\n  if (!parent) {\n    qWarning() << \"TreeOperations::openKeyIfExists > Invalid parent\";\n    return;\n  }\n\n  getReadyConnection([this, fullPath, parent,\n                      callback](QSharedPointer<RedisClient::Connection> c) {\n    c->cmd(\n        {\"exists\", fullPath}, this, static_cast<int>(parent->getDbIndex()),\n        [this, parent, fullPath, callback](RedisClient::Response r) {\n          QVariant result = r.value();\n\n          if (result.toByteArray() == \"1\") {\n            auto key = QSharedPointer<ConnectionsTree::KeyItem>(\n                new ConnectionsTree::KeyItem(fullPath, parent.toWeakRef(),\n                                             parent->model(),\n                                             parent->keysShortNameRendering()));\n\n            emit m_events->openValueTab(m_connection, key, true);\n\n            callback->call(QString(), true);\n          } else {\n            callback->call(QString(), false);\n          }\n        },\n        [callback](const QString& err) { callback->call(err, false); });\n  });\n}\n\nvoid TreeOperations::getUsedMemory(const QList<QByteArray>& keys, int dbIndex,\n                                   QSharedPointer<GetUsedMemoryCallback> result,\n                                   QSharedPointer<GetUsedMemoryCallback> progress) {\n  QList<QList<QByteArray>> commands;\n\n  for (int index = 0; index < keys.size(); ++index) {\n    commands.append({\"MEMORY\", \"USAGE\", keys[index]});\n  }\n\n  int expectedResponses = commands.size();\n  auto processedResponses = QSharedPointer<int>(new int(0));\n  auto totalMemory = QSharedPointer<qlonglong>(new qlonglong(0));\n\n  m_connection->pipelinedCmd(\n      commands, this, dbIndex,\n      [this, expectedResponses, processedResponses, totalMemory, progress,\n       result](RedisClient::Response r, QString err) {\n        if (!err.isEmpty()) {\n          QString errorMsg =\n              QCoreApplication::translate(\n                  \"RESP\", \"Cannot determine amount of used memory by key: %1\")\n                  .arg(err);\n          m_events->error(errorMsg);          \n        } else {\n          QVariant incrResult = r.value();\n\n          if (incrResult.canConvert(QVariant::LongLong)) {\n            (*totalMemory) += incrResult.toLongLong();\n            (*processedResponses)++;\n          } else if (incrResult.canConvert(QVariant::List)) {\n            auto responses = incrResult.toList();\n\n            for (auto resp : responses) {\n              (*totalMemory) += resp.toLongLong();\n              (*processedResponses)++;\n            }\n          }\n\n          if (progress)\n            progress->call(*totalMemory);\n\n          if ((*processedResponses) >= expectedResponses && result) {\n            result->call(*totalMemory);\n          }\n        }\n      }, true);\n}\n\nQString TreeOperations::mode() {\n  if (m_connectionMode == RedisClient::Connection::Mode::Cluster) {\n    return QString(\"cluster\");\n  } else if (m_connectionMode == RedisClient::Connection::Mode::Sentinel) {\n    return QString(\"sentinel\");\n  } else {\n    return QString(\"standalone\");\n  }\n}\n\nbool TreeOperations::isConnected() const { return m_connection->isConnected(); }\n\nQSharedPointer<RedisClient::Connection> TreeOperations::connection()\n{\n    return m_connection;\n}\n\nvoid TreeOperations::setConnection(QSharedPointer<RedisClient::Connection> c) {\n  m_connection = c;\n  m_events->registerLoggerForConnection(*c);\n}\n\nServerConfig TreeOperations::config()\n{\n    m_config.setOwner(sharedFromThis().toWeakRef());\n    return m_config;\n}\n\nvoid TreeOperations::setConfig(const ServerConfig &c)\n{\n    m_config = c;\n    m_config.setOwner(sharedFromThis().toWeakRef());\n    m_connection->setConnectionConfig(m_config);\n    emit configUpdated();\n}\n\nvoid TreeOperations::proceedWithSecret(const ServerConfig &c)\n{\n    m_config = c;\n    m_config.setOwner(sharedFromThis().toWeakRef());\n    m_connection->setConnectionConfig(m_config);\n\n    if (m_pendingOperation) {\n        m_pendingOperation(m_connection);\n    } else {\n        qWarning() << \"Unknown proceedWithSecret request\";\n        return;\n    }\n}\n\nQString TreeOperations::iconColor()\n{\n    return m_config.iconColor();\n}\n"
  },
  {
    "path": "src/app/models/treeoperations.h",
    "content": "﻿#pragma once\n#include <QEnableSharedFromThis>\n#include <QObject>\n#include <QSharedPointer>\n#include <functional>\n\n#include \"app/models/connectionconf.h\"\n#include \"connections-tree/items/keyitem.h\"\n#include \"modules/bulk-operations/bulkoperationsmanager.h\"\n#include \"modules/connections-tree/operations.h\"\n\nclass Events;\n\nnamespace ConnectionsTree {\nclass ServerItem;\nclass TreeItem;\n}  // namespace ConnectionsTree\n\nclass TreeOperations : public QObject,\n                       public ConnectionsTree::Operations,\n                       public QEnableSharedFromThis<TreeOperations> {\n  Q_OBJECT\n public:\n  TreeOperations(const ServerConfig& config, QSharedPointer<Events> events);\n\n  QFuture<void> getDatabases(\n      QSharedPointer<GetDatabasesCallback> callback) override;\n\n  void loadNamespaceItems(\n      uint dbIndex, const QString& filter,\n      QSharedPointer<LoadNamespaceItemsCallback> callback)\n      override;\n\n  void disconnect() override;\n\n  void resetConnection() override;\n\n  QString getNamespaceSeparator() override;\n\n  QString defaultFilter() override;\n\n  QVariantMap getFilterHistory() override;\n\n  QString connectionName() const override;\n\n  void openKeyTab(QSharedPointer<ConnectionsTree::KeyItem> key,\n                  bool openInNewTab = false) override;\n\n  void openConsoleTab(int dbIndex = 0) override;\n\n  void openNewKeyDialog(int dbIndex,\n                        QSharedPointer<OpenNewKeyDialogCallback> callback,\n                        QString keyPrefix = QString()) override;\n\n  void openServerStats() override;\n\n  void duplicateConnection() override;\n\n  void notifyDbWasUnloaded(int dbIndex) override;\n\n  void deleteDbKey(ConnectionsTree::KeyItem& key,\n                   QSharedPointer<DeleteDbKeyCallback> callback) override;\n\n  virtual void deleteDbKeys(ConnectionsTree::DatabaseItem& db) override;\n\n  void deleteDbNamespace(ConnectionsTree::NamespaceItem& ns) override;\n\n  virtual void setTTL(ConnectionsTree::AbstractNamespaceItem& ns) override;\n\n  virtual void copyKeys(ConnectionsTree::AbstractNamespaceItem& ns) override;\n\n  virtual void importKeysFromRdb(ConnectionsTree::DatabaseItem& ns) override;\n\n  virtual void flushDb(int dbIndex,\n                       QSharedPointer<FlushDbCallback> callback) override;\n\n  virtual QFuture<bool> connectionSupportsMemoryOperations() override;\n\n  virtual void openKeyIfExists(\n      const QByteArray& key,\n      QSharedPointer<ConnectionsTree::DatabaseItem> parent,\n      QSharedPointer<OpenKeyIfExistsCallback> callback) override;\n\n  virtual void getUsedMemory(const QList<QByteArray>& keys, int dbIndex,\n                             QSharedPointer<GetUsedMemoryCallback> result,\n                             QSharedPointer<GetUsedMemoryCallback> progress) override;\n\n  virtual QString mode() override;\n\n  virtual bool isConnected() const override;\n\n  QSharedPointer<RedisClient::Connection> connection();\n\n  void setConnection(QSharedPointer<RedisClient::Connection> c);\n\n  ServerConfig config();\n\n  void setConfig(const ServerConfig& c);\n\n  void proceedWithSecret(const ServerConfig& c);\n\n  QString iconColor() override;\n\nsignals:\n  void createNewConnection(const ServerConfig& config);\n\n  void configUpdated();\n\n  void filterHistoryUpdated();\n\n  void secretRequired(const ServerConfig& config, const QString& id);\n\n protected:\n  void loadDatabases(\n      QSharedPointer<RedisClient::Connection> c,\n      QSharedPointer<AsyncFuture::Deferred<void>> d,\n      std::function<void(RedisClient::DatabaseList, const QString&)> callback);\n\n  void recursiveSelectScan(\n      QSharedPointer<AsyncFuture::Deferred<void>> d,\n      QSharedPointer<RedisClient::Connection> c,\n      QSharedPointer<RedisClient::DatabaseList> dbList,\n      std::function<void(RedisClient::DatabaseList, const QString&)> callback);\n\n  bool connect(QSharedPointer<RedisClient::Connection> c);\n\n  void requestBulkOperation(\n      ConnectionsTree::AbstractNamespaceItem& ns,\n      BulkOperations::Manager::Operation op,\n      BulkOperations::AbstractOperation::OperationCallback callback);\n\n private:\n  typedef std::function<void(QSharedPointer<RedisClient::Connection>)>\n      PendingOperation;\n  void getReadyConnection(PendingOperation callback);\n\n private:\n  QSharedPointer<RedisClient::Connection> m_connection;\n  QSharedPointer<Events> m_events;\n  uint m_dbCount;\n  RedisClient::Connection::Mode m_connectionMode;\n  ServerConfig m_config;\n  QVariantMap m_filterHistory;\n  QWeakPointer<ConnectionsTree::ServerItem> m_serverItem;\n  QSharedPointer<AsyncFuture::Deferred<void>> m_dbScanOp;\n  PendingOperation m_pendingOperation;\n};\n"
  },
  {
    "path": "src/app/qcompress.cpp",
    "content": "#include \"qcompress.h\"\n\n#include <brotli/decode.h>\n#include <brotli/encode.h>\n#include <lz4.h>\n#include <lz4frame.h>\n#include <snappy.h>\n#include <zlib.h>\n#include <zstd.h>\n\n#include <QDebug>\n\n#define ZLIB_WINDOW_BIT 15 + 16\n#define ZLIB_PHP_WINDOW_BIT 15\n#define ZLIB_CHUNK_SIZE 32 * 1024\n#define ZLIB_LEVEL 6\n\n#define ZSTD_LEVEL 1\n\n#define BROTLI_BUFFER_SIZE 32 * 1024\n\nstruct LZ4FCleanUp {\n  static inline void cleanup(LZ4F_dctx *p) { LZ4F_freeDecompressionContext(p); }\n};\n\nstruct ZSTDCleanUp {\n  static inline void cleanup(ZSTD_DCtx *p) { ZSTD_freeDCtx(p); }\n};\n\nQByteArray gzipDecode(const QByteArray &val, int windowBits) {\n  z_stream strm;\n  strm.zalloc = Z_NULL;\n  strm.zfree = Z_NULL;\n  strm.opaque = Z_NULL;\n  strm.avail_in = 0;\n  strm.next_in = Z_NULL;\n  QByteArray output;\n\n  int ret = inflateInit2(&strm, windowBits);\n\n  if (ret != Z_OK) return QByteArray();\n\n  const char *input_data = val.data();\n  int input_data_left = val.length();\n\n  do {\n    int chunk_size = qMin(ZLIB_CHUNK_SIZE, input_data_left);\n\n    if (chunk_size <= 0) break;\n\n    strm.next_in = (unsigned char *)input_data;\n    strm.avail_in = chunk_size;\n\n    input_data += chunk_size;\n    input_data_left -= chunk_size;\n\n    do {\n      char out[ZLIB_CHUNK_SIZE];\n\n      strm.next_out = (unsigned char *)out;\n      strm.avail_out = ZLIB_CHUNK_SIZE;\n\n      ret = inflate(&strm, Z_NO_FLUSH);\n\n      switch (ret) {\n        case Z_NEED_DICT:\n          ret = Z_DATA_ERROR;\n        case Z_DATA_ERROR:\n        case Z_MEM_ERROR:\n        case Z_STREAM_ERROR:\n          inflateEnd(&strm);\n          return QByteArray();\n      }\n\n      int have = (ZLIB_CHUNK_SIZE - strm.avail_out);\n\n      if (have > 0) output.append((char *)out, have);\n    } while (strm.avail_out == 0);\n  } while (ret != Z_STREAM_END);\n\n  inflateEnd(&strm);\n\n  if (ret == Z_STREAM_END) {\n    return output;\n  } else {\n    return QByteArray();\n  }\n}\n\nQByteArray lz4RawDecode(const QByteArray &val) {\n  int offset = sizeof(int);\n\n  if (val.size() < offset) {\n    return QByteArray();\n  }\n\n  int dataSize;\n  memcpy(&dataSize, val.data(), offset);\n\n  QByteArray dst(dataSize, '\\x00');\n\n  auto res = LZ4_decompress_safe(val.constData() + offset, dst.data(),\n                                 val.size() - offset, dst.capacity());\n\n  if (res < 0) {\n    qWarning() << \"LZ4 raw decoding error\";\n    return QByteArray();\n  }\n\n  return dst;\n}\n\nQByteArray lz4RawEncode(const QByteArray &val) {\n  int maxSize = LZ4_compressBound(val.size());\n\n  QByteArray dst(maxSize, '\\x00');\n\n  int res = LZ4_compress_default(val.constData(), dst.data(), val.size(),\n                                 dst.capacity());\n\n  if (res == 0) {\n    qWarning() << \"LZ4 raw decoding error\";\n    return QByteArray();\n  }\n\n  return dst;\n}\n\nQByteArray lz4FrameDecode(const QByteArray &val) {\n  LZ4F_dctx *lz4_dctx = nullptr;\n  LZ4F_createDecompressionContext(&lz4_dctx, LZ4F_VERSION);\n\n  if (!lz4_dctx) {\n    qWarning() << \"LZ4 error. Cannot initialize context\";\n    return QByteArray();\n  }\n\n  QScopedPointer<LZ4F_dctx, LZ4FCleanUp> dctx(lz4_dctx);\n\n  LZ4F_frameInfo_t lz4_frameinfo;\n  size_t buffSize = val.size();\n\n  size_t res = LZ4F_getFrameInfo(dctx.data(), &lz4_frameinfo,\n                                 static_cast<const void *>(val.constData()),\n                                 static_cast<size_t *>(&buffSize));\n\n  if (LZ4F_isError(res)) {\n    qWarning() << \"LZ4 error. Cannot retrive frame info\";\n    return QByteArray();\n  }\n\n  size_t contentSize = lz4_frameinfo.contentSize;\n  size_t srcSize = val.size();\n\n  if (!(0 < contentSize && contentSize <= 255 * srcSize)) {\n    return QByteArray();\n  }\n\n  QByteArray dst(contentSize, '\\x00');\n  size_t dstSize = dst.size();\n\n  static constexpr const LZ4F_decompressOptions_t opt{};\n\n  res = LZ4F_decompress(dctx.data(), dst.data(), &dstSize,\n                        val.data() + buffSize, &srcSize, &opt);\n\n  if (LZ4F_isError(res)) {\n    qWarning() << \"LZ4 error. Cannot decode frame\" << LZ4F_getErrorName(res);\n    return QByteArray();\n  }\n\n  return dst;\n}\n\nQByteArray gzipEncode(const QByteArray &val, int windowBits) {\n  int flush = 0;\n\n  z_stream strm;\n  strm.zalloc = Z_NULL;\n  strm.zfree = Z_NULL;\n  strm.opaque = Z_NULL;\n  strm.avail_in = 0;\n  strm.next_in = Z_NULL;\n  QByteArray output;\n\n  int ret = deflateInit2(&strm, qMax(-1, qMin(9, ZLIB_LEVEL)), Z_DEFLATED,\n                         windowBits, 8, Z_DEFAULT_STRATEGY);\n\n  if (ret != Z_OK) return output;\n\n  const char *input_data = val.data();\n  int input_data_left = val.length();\n\n  do {\n    int chunk_size = qMin(ZLIB_CHUNK_SIZE, input_data_left);\n\n    strm.next_in = (unsigned char *)input_data;\n    strm.avail_in = chunk_size;\n\n    input_data += chunk_size;\n    input_data_left -= chunk_size;\n\n    flush = (input_data_left <= 0 ? Z_FINISH : Z_NO_FLUSH);\n\n    do {\n      char out[ZLIB_CHUNK_SIZE];\n\n      strm.next_out = (unsigned char *)out;\n      strm.avail_out = ZLIB_CHUNK_SIZE;\n\n      ret = deflate(&strm, flush);\n\n      if (ret == Z_STREAM_ERROR) {\n        deflateEnd(&strm);\n        return QByteArray();\n      }\n\n      int have = (ZLIB_CHUNK_SIZE - strm.avail_out);\n\n      if (have > 0) output.append((char *)out, have);\n    } while (strm.avail_out == 0);\n  } while (flush != Z_FINISH);\n\n  (void)deflateEnd(&strm);\n\n  if (ret == Z_STREAM_END) {\n    return output;\n  } else {\n    return QByteArray();\n  }\n}\n\nQByteArray lz4FrameEncode(const QByteArray &val) {\n  QByteArray dst;\n  LZ4F_preferences_t opt{};\n  opt.frameInfo.contentSize = val.size();\n  size_t expectedSize = LZ4F_compressFrameBound(val.size(), &opt);\n  dst.resize(expectedSize);\n\n  size_t res =\n      LZ4F_compressFrame(dst.data(), dst.size(), val.data(), val.size(), &opt);\n\n  if (LZ4F_isError(res)) {\n    qWarning() << \"LZ4 error. Cannot compress frame\" << LZ4F_getErrorName(res);\n    return QByteArray();\n  }\n\n  if (expectedSize > res) {\n      dst.resize(res);\n  }\n\n  return dst;\n}\n\nQByteArray zstdDecode(const QByteArray &val) {\n  size_t buffSize = val.size();\n  auto decompressedSize = ZSTD_getFrameContentSize(\n      static_cast<const void *>(val.constData()), buffSize);\n\n  if (decompressedSize == 0UL || decompressedSize == ZSTD_CONTENTSIZE_ERROR) {\n    return QByteArray();\n  }\n\n  size_t srcSize = val.size();  \n\n  ZSTD_DCtx *const zstd_dctx = ZSTD_createDCtx();\n\n  if (!zstd_dctx) {\n    qWarning() << \"ZSTD error. Cannot initialize context\";\n    return QByteArray();\n  }\n\n  QScopedPointer<ZSTD_DCtx, ZSTDCleanUp> dctx(zstd_dctx);\n\n  QByteArray dst(decompressedSize, '\\x00');\n  size_t dstSize = dst.size();\n\n  size_t const res =\n      ZSTD_decompress(static_cast<void *>(dst.data()), dstSize,\n                      static_cast<const void *>(val.data()), srcSize);\n\n  if (ZSTD_isError(res)) {\n    qWarning() << \"ZSTD error. Cannot decode frame\" << ZSTD_getErrorName(res);\n    return QByteArray();\n  }\n\n  dst.resize(res);\n\n  return dst;\n}\n\nQByteArray zstdEncode(const QByteArray &val) {\n  QByteArray dst;\n  dst.resize(ZSTD_compressBound(val.size()));\n\n  size_t res =\n      ZSTD_compress(dst.data(), dst.size(), val.data(), val.size(), ZSTD_LEVEL);\n\n  if (ZSTD_isError(res)) {\n    qWarning() << \"ZSTD error. Cannot compress frame\" << ZSTD_getErrorName(res);\n    return QByteArray();\n  }\n\n  dst.resize(res);\n\n  return dst;\n}\n\nQByteArray snappyDecode(const QByteArray &val) {\n  size_t size = 0;\n\n  bool res = snappy::GetUncompressedLength(val.constData(), val.size(), &size);\n\n  if (!res) {\n    qWarning() << \"Snappy error: Cannot get uncompressed size\";\n    QByteArray();\n  }\n\n  std::string output;\n\n  res = snappy::Uncompress(val.constData(), val.size(), &output);\n\n  if (!res) {\n    qWarning() << \"Snappy error: Cannot uncompress buffer\";\n    QByteArray();\n  }\n\n  return QByteArray::fromStdString(output);\n}\n\nQByteArray snappyEncode(const QByteArray &val) {\n  std::string output;\n\n  bool res = snappy::Compress(val.constData(), val.size(), &output);\n\n  if (!res) {\n    qWarning() << \"Snappy error: Cannot compress buffer\";\n    QByteArray();\n  }\n\n  return QByteArray::fromStdString(output);\n}\n\nQByteArray brotliDecode(const QByteArray &val)\n{\n    auto decoder = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);\n\n    if (!decoder) {\n        qWarning() << \"BROTLI: Cannot create decoder\";\n        return QByteArray();\n    }\n\n    QByteArray dst;\n    dst.resize(BROTLI_BUFFER_SIZE);\n\n    size_t availableIn = val.size(), availableOut = dst.size();\n    const uint8_t* nextIn = reinterpret_cast<const uint8_t*>(val.constData());\n    uint8_t* nextOut = reinterpret_cast<uint8_t*>(dst.data());\n    BrotliDecoderResult itResult;\n\n    do {\n      itResult = BrotliDecoderDecompressStream(\n          decoder, &availableIn, &nextIn, &availableOut, &nextOut, nullptr);\n      if (itResult == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {\n        size_t offset = dst.size() - availableOut;\n        availableOut += dst.size();\n        dst.resize(dst.size() * 2);\n        nextOut = reinterpret_cast<uint8_t *>(dst.data()) + offset;\n        itResult = BROTLI_DECODER_RESULT_SUCCESS;\n      }\n\n      if (itResult != BROTLI_DECODER_RESULT_SUCCESS) {\n          qWarning() << \"Brotli: Invalid input\";\n          return QByteArray();\n      }\n    } while (!(availableIn == 0 &&\n               itResult == BROTLI_DECODER_RESULT_SUCCESS));\n\n    if (itResult == BROTLI_DECODER_RESULT_SUCCESS)\n        dst.resize(dst.size() - availableOut);\n\n    BrotliDecoderDestroyInstance(decoder);\n    return dst;\n}\n\nQByteArray brotliEncode(const QByteArray &val)\n{\n    auto encoder = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);\n\n    if (!encoder) {\n        qWarning() << \"BROTLI: Cannot create encoder\";\n        return QByteArray();\n    }\n\n    QByteArray dst;\n    dst.resize(BROTLI_BUFFER_SIZE);\n\n    size_t availableIn = val.size(), available_out = dst.size();\n    const uint8_t* nextIn = reinterpret_cast<const uint8_t*>(val.constData());\n    uint8_t* nextOut = reinterpret_cast<uint8_t*>(dst.data());\n    size_t totalOut = 0;\n\n    int itResult;\n\n    do\n    {\n        itResult = BrotliEncoderCompressStream\n        (\n            encoder, BROTLI_OPERATION_FINISH,\n            &availableIn, &nextIn, &available_out, &nextOut, &totalOut\n        );\n    }\n    while (!(availableIn == 0 && BrotliEncoderIsFinished(encoder)));\n\n    if (itResult == BROTLI_TRUE) {\n        dst.resize(totalOut);\n    }\n\n    BrotliEncoderDestroyInstance(encoder);\n    return dst;\n}\n\nbool validateLZ4Frame(const QByteArray &val) {\n  const auto magicHeader = QByteArray::fromHex(\"x04x22x4dx18\");\n\n  if (!val.startsWith(magicHeader)) return false;\n\n  LZ4F_dctx *lz4_dctx = nullptr;\n  LZ4F_createDecompressionContext(&lz4_dctx, LZ4F_VERSION);\n\n  if (!lz4_dctx) {\n    qWarning() << \"LZ4 error. Cannot initialize context\";\n    return false;\n  }\n\n  QScopedPointer<LZ4F_dctx, LZ4FCleanUp> dctx(lz4_dctx);\n\n  LZ4F_frameInfo_t lz4_frameinfo;\n  size_t buffSize = val.size();\n\n  size_t res = LZ4F_getFrameInfo(dctx.data(), &lz4_frameinfo,\n                                 static_cast<const void *>(val.constData()),\n                                 static_cast<size_t *>(&buffSize));\n\n  if (LZ4F_isError(res)) {\n    qWarning() << \"LZ4 error. Cannot retrive frame info\";\n    return false;\n  }\n\n  return lz4_frameinfo.contentSize > 0;\n}\n\nbool validateZSTDFrame(const QByteArray &val) {\n  const auto magicHeader = QByteArray::fromHex(\"x28xB5x2FxFD\");\n\n  if (!val.startsWith(magicHeader)) {\n    return false;\n  }\n  size_t buffSize = val.size();\n  unsigned long long decompressedSize = ZSTD_getFrameContentSize(\n      static_cast<const void *>(val.constData()), buffSize);\n\n  return !(decompressedSize == ZSTD_CONTENTSIZE_ERROR);\n}\n\nbool validateGZip(const QByteArray &val, int from = 0) {\n  return val.indexOf(QByteArray::fromHex(\"x1fx8b\"), from) == 0;\n}\n\nbool validateSnappyFrame(const QByteArray &val) {\n  return snappy::IsValidCompressedBuffer(val.constData(), val.size());\n}\n\nbool isMagentoCacheFormat(unsigned f) {\n  return qcompress::MAGENTO_CACHE_GZIP <= f &&\n         f <= qcompress::MAGENTO_CACHE_SNAPPY;\n}\n\nQHash<unsigned, QByteArray> knownMagentoFormats() {\n  return {\n      {qcompress::MAGENTO_CACHE_GZIP, \"gz\"},\n      {qcompress::MAGENTO_SESSION_GZIP, \"gz\"},\n      {qcompress::MAGENTO_CACHE_LZ4, \"l4\"},\n      {qcompress::MAGENTO_SESSION_LZ4, \"l4\"},\n      {qcompress::MAGENTO_CACHE_ZSTD, \"zs\"},\n      {qcompress::MAGENTO_SESSION_SNAPPY, \"sn\"},\n      {qcompress::MAGENTO_CACHE_SNAPPY, \"sn\"},\n  };\n}\n\nstatic const QHash<unsigned, QByteArray> magentoFormats = knownMagentoFormats();\n\nQByteArray magentoPrefix(unsigned f) {\n  if (!magentoFormats.contains(f)) {\n    return QByteArray();\n  }\n\n  QByteArray id = magentoFormats[f];\n\n  if (isMagentoCacheFormat(f)) {\n    id += QByteArray(\":\") + QByteArray::fromHex(\"x1fx8b\");\n  } else {\n    id = QByteArray(\":\") + id + QByteArray(\":\");\n  }\n\n  return id;\n}\n\nunsigned qcompress::guessFormat(const QByteArray &val) {\n  if (val.size() > 4) {\n    auto mFormats = magentoFormats.keys();\n\n    QByteArray prefix;\n\n    for (auto f : qAsConst(mFormats)) {\n      prefix = magentoPrefix(f);\n\n      if (val.startsWith(prefix)) {\n        return f;\n      }\n    }\n  }\n\n  if (val.size() > 2 && validateGZip(val)) {\n    return qcompress::GZIP;\n  } else if (val.size() > 4 && validateLZ4Frame(val)) {\n    return qcompress::LZ4;\n  } else if (val.size() > 4 && validateZSTDFrame(val)) {\n    return qcompress::ZSTD;\n  } else if (val.size() > 10 && validateSnappyFrame(val)) {\n    return qcompress::SNAPPY;\n  }\n\n  return qcompress::UNKNOWN;\n}\n\nQByteArray qcompress::compress(const QByteArray &val, unsigned algo) {\n  switch (algo) {\n    case qcompress::GZIP:\n      return gzipEncode(val, ZLIB_WINDOW_BIT);\n    case qcompress::MAGENTO_SESSION_GZIP:\n    case qcompress::MAGENTO_CACHE_GZIP:\n      return magentoPrefix(algo) + gzipEncode(val, ZLIB_PHP_WINDOW_BIT);\n    case qcompress::LZ4:\n      return lz4FrameEncode(val);\n    case qcompress::MAGENTO_SESSION_LZ4:\n    case qcompress::MAGENTO_CACHE_LZ4:\n      return magentoPrefix(algo) + lz4RawEncode(val);\n    case qcompress::ZSTD:\n      return zstdEncode(val);\n    case qcompress::MAGENTO_CACHE_ZSTD:\n      return magentoPrefix(algo) + zstdEncode(val);\n    case qcompress::SNAPPY:\n      return snappyEncode(val);\n    case qcompress::MAGENTO_CACHE_SNAPPY:\n    case qcompress::MAGENTO_SESSION_SNAPPY:\n      return magentoPrefix(algo) + snappyEncode(val);\n    case qcompress::BROTLI:\n      return brotliEncode(val);\n    default:\n      return QByteArray();\n  }\n}\n\nQByteArray qcompress::decompress(const QByteArray &val, unsigned format) {\n  int offset = 0;\n\n  if (magentoFormats.contains(format)) {\n    offset = magentoPrefix(format).size();\n  }\n\n  switch (format) {\n    case qcompress::GZIP:\n      return gzipDecode(val, ZLIB_WINDOW_BIT);\n    case qcompress::MAGENTO_SESSION_GZIP:\n    case qcompress::MAGENTO_CACHE_GZIP:\n    case qcompress::GZIP_PHP:\n      return gzipDecode(val.mid(offset), ZLIB_PHP_WINDOW_BIT);\n    case qcompress::LZ4:\n      return lz4FrameDecode(val);\n    case qcompress::MAGENTO_SESSION_LZ4:\n    case qcompress::MAGENTO_CACHE_LZ4:\n    case qcompress::LZ4_RAW:\n      return lz4RawDecode(val.mid(offset));\n    case qcompress::ZSTD:\n      return zstdDecode(val);\n    case qcompress::MAGENTO_CACHE_ZSTD:\n      return zstdDecode(val.mid(offset));\n    case qcompress::SNAPPY:\n      return snappyDecode(val);\n    case qcompress::MAGENTO_CACHE_SNAPPY:\n    case qcompress::MAGENTO_SESSION_SNAPPY:\n      return snappyDecode(val.mid(offset));\n    case qcompress::BROTLI:\n      return brotliDecode(val);\n    default:\n      return QByteArray();\n  }\n}\n\nQString qcompress::nameOf(unsigned alg) {\n  switch (alg) {\n    case qcompress::GZIP:\n      return \"gzip\";\n    case qcompress::LZ4:\n      return \"lz4\";\n    case qcompress::MAGENTO_SESSION_GZIP:\n      return \"magento-session-gzip\";\n    case qcompress::MAGENTO_SESSION_LZ4:\n      return \"magento-session-lz4\";\n    case qcompress::MAGENTO_CACHE_GZIP:\n      return \"magento-cache-gzip\";\n    case qcompress::MAGENTO_CACHE_LZ4:\n      return \"magento-cache-lz4\";\n    case qcompress::MAGENTO_CACHE_ZSTD:\n      return \"magento-cache-zstd\";\n    case qcompress::MAGENTO_CACHE_SNAPPY:\n      return \"magento-cache-snappy\";\n    case qcompress::MAGENTO_SESSION_SNAPPY:\n      return \"magento-session-snappy\";\n    case qcompress::ZSTD:\n      return \"ZSTD\";\n    case qcompress::SNAPPY:\n      return \"Snappy\";\n    case qcompress::GZIP_PHP:\n      return \"PHP gzcompress\";\n    case qcompress::LZ4_RAW:\n      return \"LZ4 Raw\";\n    case qcompress::BROTLI:\n      return \"Brotli\";\n    case qcompress::UNKNOWN:\n    default:\n      return \"unknown\";\n  }\n}\n"
  },
  {
    "path": "src/app/qcompress.h",
    "content": "#pragma once\n#include <QByteArray>\n#include <QString>\n\nnamespace qcompress {\n\nenum {\n  UNKNOWN,\n  GZIP,\n  LZ4,\n  ZSTD,\n  /*\n   * MAGENTO session is build on top of the following php lib to store\n   *compressed sessions:\n   * https://github.com/colinmollenhour/php-redis-session-abstract/blob/5f399c53534cd1fe07460407e510590840b2c6d0/src/Cm/RedisSession/Handler.php#L822-L828\n   **/\n  MAGENTO_SESSION_GZIP,\n  MAGENTO_SESSION_LZ4,\n  MAGENTO_SESSION_SNAPPY,\n  // MAGENTO_SESSION_ZSTD, // ZSTD is not yet supported -\n  // https://github.com/colinmollenhour/php-redis-session-abstract/issues/42\n\n  /*\n   * MAGENTO CACHE\n   * https://github.com/colinmollenhour/Cm_Cache_Backend_Redis/blob/a9c4a5ae6001e04097aa7302abd89c9496c563e0/Cm/Cache/Backend/Redis.php#L1202-L1207\n   */\n  MAGENTO_CACHE_GZIP,\n  MAGENTO_CACHE_LZ4,\n  MAGENTO_CACHE_ZSTD,\n  MAGENTO_CACHE_SNAPPY,\n\n  SNAPPY,\n  GZIP_PHP,\n  LZ4_RAW,\n  BROTLI\n};\n\nunsigned guessFormat(const QByteArray& val);\n\nQString nameOf(unsigned alg);\n\nQByteArray decompress(const QByteArray& val, unsigned algo);\n\nQByteArray compress(const QByteArray& val, unsigned algo);\n\n}  // namespace qcompress\n"
  },
  {
    "path": "src/app/qmlutils.cpp",
    "content": "#include \"qmlutils.h\"\n#include <qredisclient/utils/text.h>\n#include <qtextdocumentfragment.h>\n#include <QApplication>\n#include <QClipboard>\n#include <QDateTime>\n#include <QDebug>\n#include <QDir>\n#include <QFile>\n#include <QFileInfo>\n#include <QScreen>\n#include <QtCharts/QDateTimeAxis>\n#include <QtConcurrent>\n#include <QUrl>\n\n#include \"apputils.h\"\n#include \"jsonutils.h\"\n#include \"qcompress.h\"\n#include \"value-editor/largetextmodel.h\"\n\n#define MAX_CHART_DATA_POINTS 1000\n\nbool QmlUtils::isBinaryString(const QVariant &value) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return false;\n  }\n  QByteArray val = value.toByteArray();   \n\n  return isBinary(val);\n}\n\nlong QmlUtils::binaryStringLength(const QVariant &value) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return -1;\n  }\n  QByteArray val = value.toByteArray();\n  return val.size();\n}\n\nQVariant QmlUtils::b64toByteArray(const QVariant &value)\n{\n    if (!value.canConvert(QVariant::String)) {\n      return -1;\n    }\n\n    return QVariant(QByteArray::fromBase64(value.toString().toUtf8()));\n}\n\nQByteArray QmlUtils::minifyJSON(const QVariant &value)\n{\n    if (!value.canConvert(QVariant::ByteArray)) {\n      return QByteArray();\n    }\n\n    QByteArray val = value.toByteArray();\n\n    return JSONUtils::minifyJSON(val);\n}\n\nQByteArray QmlUtils::prettyPrintJSON(const QVariant &value)\n{\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return QByteArray();\n  }\n\n  QByteArray val = value.toByteArray();\n  return JSONUtils::prettyPrintJSON(val);\n}\n\nbool QmlUtils::isJSON(const QVariant &value)\n{\n    if (!value.canConvert(QVariant::ByteArray)) {\n      return false;\n    }\n\n    QByteArray val = value.toByteArray();\n    return JSONUtils::isJSON(val);\n}\n\nQVariant QmlUtils::decompress(const QVariant &value, unsigned alg) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return 0;\n  }\n\n  return qcompress::decompress(value.toByteArray(), alg);\n}\n\nQVariant QmlUtils::compress(const QVariant &value, unsigned alg) {\n  return qcompress::compress(value.toByteArray(), alg);\n}\n\nunsigned QmlUtils::isCompressed(const QVariant &value) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return 0;\n  }\n\n  return qcompress::guessFormat(value.toByteArray());\n}\n\nQString QmlUtils::compressionAlgName(unsigned alg) {\n    return qcompress::nameOf(alg);\n}\n\nQVariant QmlUtils::compressionMethodsNoMagic()\n{\n    QVariantList methodsWithoutMagicHeaders = {\n        qcompress::UNKNOWN,\n        qcompress::BROTLI,\n        qcompress::LZ4_RAW,\n\n        //NOTE(u_glide): Should be always last in this list\n        // QML side use it for conditions\n        qcompress::GZIP_PHP,\n    };\n\n    return methodsWithoutMagicHeaders;\n}\n\nQString QmlUtils::humanSize(long size) { return humanReadableSize(size); }\n\nQVariant QmlUtils::valueToBinary(const QVariant &value) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return QVariant();\n  }\n\n  QByteArray val = value.toByteArray();\n  QVariantList list;\n\n  for (int index = 0; index < val.length(); ++index) {\n    list.append(QVariant((unsigned char)val.at(index)));\n  }\n  return QVariant(list);\n}\n\nQVariant QmlUtils::binaryListToValue(const QVariantList &binaryList) {\n  QByteArray value;\n  foreach (QVariant v, binaryList) { value.append((unsigned char)v.toInt()); }\n  return value;\n}\n\nQVariant QmlUtils::printable(const QVariant &value, bool htmlEscaped, int maxLength) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return QVariant();\n  }\n\n  QByteArray val = value.toByteArray();\n\n  if (maxLength > 0 && val.size() > maxLength) {\n    val.truncate(maxLength);\n  }\n\n  if (htmlEscaped) {\n    return printableString(val).toHtmlEscaped();\n  } else {\n    return printableString(val);\n  }\n}\n\nQVariant QmlUtils::printableToValue(const QVariant &printable) {\n  if (!printable.canConvert(QVariant::String)) {\n    return QVariant();\n  }\n  QString val = printable.toString();\n  return printableStringToBinary(val);\n}\n\nQVariant QmlUtils::toUtf(const QVariant &value) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return QVariant();\n  }\n  QByteArray val = value.toByteArray();\n  QString result = QString::fromUtf8(val.constData(), val.size());\n  return QVariant(result);\n}\n\nQString QmlUtils::getNativePath(const QString &path) {\n  return QDir::toNativeSeparators(path);\n}\n\nQString QmlUtils::getPathFromUrl(const QUrl &url) {\n  return url.isLocalFile() ? url.toLocalFile() : url.path();\n}\n\nQString QmlUtils::getUrlFromPath(const QString &path) {\n  return QUrl::fromLocalFile(path).toString();\n}\n\nQString QmlUtils::getDir(const QString &path) {\n  return QFileInfo(path).absoluteDir().absolutePath();\n}\n\nbool QmlUtils::fileExists(const QString &path) {\n    return QFileInfo::exists(path);\n}\n\nQString QmlUtils::replaceColorsInSvg(const QString &path, QVariant mapping)\n{\n    QFile svgFile(path.mid(3));\n    if (!svgFile.open(QIODevice::ReadOnly)) {\n        qWarning() << \"Cannot open svg:\" << path.mid(3);\n        return QString();\n    }\n\n    if (!mapping.canConvert<QVariantMap>()) {\n        qWarning() << \"Invalid colors mapping:\" << mapping;\n        return QString();\n    }\n\n    QVariantMap colors = mapping.toMap();\n\n    QString svgData = QString::fromUtf8(svgFile.readAll());\n\n    auto it = colors.constBegin();\n\n    while (it != colors.constEnd()) {\n        auto originalColor = it.key();\n        auto newColor = QColor(it.value().toString());\n\n        if (newColor.alphaF() < 1) {\n            svgData.replace(originalColor, QString(\"%1\\\" fill-opacity=\\\"%2\").arg(newColor.name()).arg(newColor.alphaF()));\n        } else {\n            svgData = svgData.replace(originalColor, newColor.name());\n        }\n        ++it;\n    }\n\n    return QString(\"data:image/svg+xml;utf8,%1\").arg(svgData);\n\n}\n\nQString QmlUtils::changeColorAlpha(QColor c, int a)\n{\n    c.setAlpha(a);\n    return c.name(QColor::HexArgb);\n}\n\nvoid QmlUtils::copyToClipboard(const QString &text) {\n  QClipboard *cb = QApplication::clipboard();\n\n  if (!cb) return;\n\n  cb->clear();\n  cb->setText(text);\n}\n\nbool QmlUtils::saveToFile(const QVariant &value, const QString &path) {\n  if (!value.canConvert(QVariant::ByteArray)) {\n    return false;\n  }\n\n  QtConcurrent::run([value, path]() {\n    QByteArray val = value.toByteArray();\n\n    QFile outputFile(path);\n    if (outputFile.open(QIODevice::WriteOnly)) {\n      QDataStream outStream(&outputFile);\n      outStream.writeRawData(val, val.size());\n      outputFile.close();\n      return true;\n    }\n    return false;\n  });\n\n  return true;\n}\n\nQtCharts::QDateTimeAxis *findDateTimeAxis(QtCharts::QXYSeries *series) {\n  using namespace QtCharts;\n\n  QList<QAbstractAxis *> axes = series->attachedAxes();\n\n  QDateTimeAxis *ax = nullptr;\n\n  for (QAbstractAxis *axis : axes) {\n    if (axis->type() == QAbstractAxis::AxisTypeDateTime) {\n      ax = qobject_cast<QDateTimeAxis *>(axis);\n      return ax;\n    }\n  }\n\n  return ax;\n}\n\nvoid QmlUtils::addNewValueToDynamicChart(QtCharts::QXYSeries *series,\n                                         qreal value) {\n  using namespace QtCharts;\n\n  QDateTimeAxis *ax = findDateTimeAxis(series);\n\n  if (!(ax && series)) {\n      qWarning() << \"Cannot add value to dynamic chart. Invalid pointers.\";\n      return;\n  }\n\n  int totalPoints = series->count();\n\n  if (totalPoints == 0) {\n    ax->setMin(QDateTime::currentDateTime());\n  }\n\n  bool dataNotChangedLastFivePoints = totalPoints > 10;\n\n  for (int i = 1; dataNotChangedLastFivePoints && i < 6; i++) {\n    if (value != series->at(totalPoints - i).y()) {\n      dataNotChangedLastFivePoints = false;\n      break;\n    }\n  }\n\n  if (dataNotChangedLastFivePoints) {\n    series->replace(totalPoints - 1, QDateTime::currentDateTime().toMSecsSinceEpoch(), value);\n  } else {\n    series->append(QDateTime::currentDateTime().toMSecsSinceEpoch(), value);\n  }\n\n  if (totalPoints > MAX_CHART_DATA_POINTS) {\n      series->removePoints(0, totalPoints - MAX_CHART_DATA_POINTS);\n      ax->setMin(QDateTime::fromMSecsSinceEpoch(series->at(0).x()));\n  }\n\n  if (series->attachedAxes().size() > 0) {\n    ax->setMax(QDateTime::currentDateTime());\n  }\n}\n\nQObject *QmlUtils::wrapLargeText(const QByteArray &text) {\n  // NOTE(u_glide): Use 50Kb chunks by default\n  int chunkSize = 50000;\n\n  auto w = new ValueEditor::LargeTextWrappingModel(QString::fromUtf8(text),\n                                                   chunkSize);\n  w->setParent(this);\n  return w;\n}\n\nvoid QmlUtils::deleteTextWrapper(QObject *w) {\n  if (w && w->parent() == this) {\n    w->deleteLater();\n  }\n}\n\nQString QmlUtils::escapeHtmlEntities(const QString &t) {\n  return t.toHtmlEscaped();\n}\n\nQString QmlUtils::standardKeyToString(QKeySequence::StandardKey key) {\n  return QKeySequence(key).toString(QKeySequence::NativeText);\n}\n\ndouble QmlUtils::getScreenScaleFactor() {\n    return QApplication::primaryScreen()->logicalDotsPerInch() / 96;\n}\n\nbool QmlUtils::isAppStoreBuild()\n{\n#ifdef RDM_APPSTORE\n    return true;\n#else\n    return false;\n#endif\n}\n"
  },
  {
    "path": "src/app/qmlutils.h",
    "content": "#pragma once\n#include <QObject>\n#include <QVariant>\n#include <QVariantList>\n#include <QUrl>\n#include <QtCharts/QXYSeries>\n#include <QKeySequence>\n\nclass QmlUtils : public QObject\n{\n    Q_OBJECT\npublic:\n    Q_INVOKABLE bool isBinaryString(const QVariant &value);\n    Q_INVOKABLE long binaryStringLength(const QVariant &value);    \n    Q_INVOKABLE QVariant b64toByteArray(const QVariant &value);\n    Q_INVOKABLE QByteArray minifyJSON(const QVariant &value);\n    Q_INVOKABLE QByteArray prettyPrintJSON(const QVariant &value);\n    Q_INVOKABLE bool isJSON(const QVariant &value);\n\n    Q_INVOKABLE unsigned isCompressed(const QVariant &value);\n    Q_INVOKABLE QVariant decompress(const QVariant &value, unsigned alg);\n    Q_INVOKABLE QVariant compress(const QVariant &value, unsigned alg);\n    Q_INVOKABLE QString compressionAlgName(unsigned alg);\n    Q_INVOKABLE QVariant compressionMethodsNoMagic();\n\n    Q_INVOKABLE QString humanSize(long size);\n    Q_INVOKABLE QVariant valueToBinary(const QVariant &value);\n    Q_INVOKABLE QVariant binaryListToValue(const QVariantList& binaryList);\n    Q_INVOKABLE QVariant printable(const QVariant &value, bool htmlEscaped=false, int maxLength=-1);\n    Q_INVOKABLE QVariant printableToValue(const QVariant &printable);\n    Q_INVOKABLE QVariant toUtf(const QVariant &value);\n    Q_INVOKABLE QString getNativePath(const QString &path);\n    Q_INVOKABLE QString getPathFromUrl(const QUrl &url);\n    Q_INVOKABLE QString getUrlFromPath(const QString &path);\n    Q_INVOKABLE QString getDir(const QString &path);\n    Q_INVOKABLE bool fileExists(const QString& path);\n\n    Q_INVOKABLE QString replaceColorsInSvg(const QString& path, QVariant mapping);\n    Q_INVOKABLE QString changeColorAlpha(QColor c, int a);\n\n    Q_INVOKABLE void copyToClipboard(const QString &text);\n    Q_INVOKABLE bool saveToFile(const QVariant &value, const QString &path);\n    Q_INVOKABLE void addNewValueToDynamicChart(QtCharts::QXYSeries* series, qreal value);\n    Q_INVOKABLE QObject* wrapLargeText(const QByteArray &text);\n    Q_INVOKABLE void deleteTextWrapper(QObject* w);\n    Q_INVOKABLE QString escapeHtmlEntities(const QString& t);\n    Q_INVOKABLE QString standardKeyToString(QKeySequence::StandardKey key);\n    Q_INVOKABLE double getScreenScaleFactor();    \n    Q_INVOKABLE bool isAppStoreBuild();\n};\n"
  },
  {
    "path": "src/main.cpp",
    "content": "#include <QDir>\n#include <QFileInfo>\n#include <QGuiApplication>\n#include <QScreen>\n#include <QDebug>\n\n#if defined(Q_OS_WIN) | defined(Q_OS_LINUX)\n#include <QProcess>\n#define RELAUNCH_CODE 1001\n#endif\n\n#ifdef CRASHPAD_INTEGRATION\n#include \"crashpad/handler.h\"\n#endif\n\n#ifdef LINUX_SIGNALS\n#include <sigwatch.h>\n#endif\n\n#ifdef IGNORE_QML_WARNINGS\nstatic const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER =\n    qInstallMessageHandler(0);\n\nvoid customMessageHandler(QtMsgType type, const QMessageLogContext &context,\n                          const QString &msg) {\n  switch (type) {\n    case QtWarningMsg: {\n      if (!msg.contains(\"QML IconLabel\")) {\n        (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);\n      }\n    } break;\n    default:  // Call the default handler.\n      (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);\n      break;\n  }\n}\n#endif\n\n#include \"app/app.h\"\n\nint main(int argc, char *argv[])\n{             \n    int returnCode = 0;\n\n#ifdef CRASHPAD_INTEGRATION\n    QFileInfo appPath(QString::fromLocal8Bit(argv[0]));\n    QString appDir(appPath.absoluteDir().path());\n    startCrashpad(appDir);\n#endif\n\n#if defined(Q_OS_WIN) || defined(Q_OS_LINUX)\n    bool disableAutoScaling = false;\n\n\n#ifndef DISABLE_SCALING_TEST\n    {\n        QGuiApplication tmp(argc, argv);\n        disableAutoScaling = QGuiApplication::primaryScreen()\n                        && QGuiApplication::primaryScreen()->availableSize().width() <= 1920\n                        && QGuiApplication::primaryScreen()->devicePixelRatio() == 1;\n    }\n#endif\n\n    if (disableAutoScaling) {\n        qDebug() << \"Disable auto-scaling\";\n        QGuiApplication::setAttribute(Qt::AA_DisableHighDpiScaling);\n    } else {\n        qDebug() << \"Enable auto-scaling\";\n        QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);\n    }\n#endif\n\n    Application a(argc, argv);\n\n#ifdef IGNORE_QML_WARNINGS\n    qInstallMessageHandler(customMessageHandler);\n#endif\n\n#ifdef LINUX_SIGNALS\n    UnixSignalWatcher sigwatch;\n    sigwatch.watchForSignal(SIGINT);\n    sigwatch.watchForSignal(SIGTERM);\n    QObject::connect(&sigwatch, SIGNAL(unixSignal(int)), &a, SLOT(quit()));\n#endif\n    a.initModels();\n    a.initQml();\n    returnCode = a.exec();\n\n#if defined(Q_OS_WIN) | defined(Q_OS_LINUX)\n    if (returnCode == RELAUNCH_CODE) {\n        QProcess::startDetached(a.arguments()[0], a.arguments());\n        returnCode = 0;\n    }\n#endif\n\n    return returnCode;\n}\n\n"
  },
  {
    "path": "src/modules/bulk-operations/bulkoperationsmanager.cpp",
    "content": "#include \"bulkoperationsmanager.h\"\n#include <qredisclient/connection.h>\n#include <QDebug>\n\n#include <qpython.h>\n\n#include \"operations/copyoperation.h\"\n#include \"operations/deleteoperation.h\"\n#include \"operations/rdbimport.h\"\n#include \"operations/ttloperation.h\"\n\nBulkOperations::Manager::Manager(QSharedPointer<ConnectionsModel> model)\n    : QObject(nullptr), m_model(model), m_python(nullptr) {\n    Q_ASSERT(m_model);\n}\n\nvoid BulkOperations::Manager::setPython(QSharedPointer<QPython> p)\n{\n    if (!p) {\n        qWarning() << \"Invalid python instance passed to BulkOperations\";\n        return;\n    }\n\n    m_python = p;\n}\n\nbool BulkOperations::Manager::hasOperation() const {\n  return !m_operation.isNull();\n}\n\nbool BulkOperations::Manager::multiConnectionOperation() const {\n  return m_operation && m_operation->multiConnectionOperation();\n}\n\nbool BulkOperations::Manager::clearOperation() {\n  if (!hasOperation()) return true;\n\n  if (m_operation->isRunning()) {\n    return false;\n  }\n\n  m_operation.clear();\n  return true;\n}\n\nvoid BulkOperations::Manager::runOperation(int connectionIndex, int dbIndex) {\n  if (!hasOperation()) return;\n\n  if (m_operation->multiConnectionOperation()) {\n    if (!(connectionIndex >= 0 && dbIndex >= 0 &&\n          m_model->getByIndex(connectionIndex))) {\n      qWarning() << \"invalid target connection\";\n      return;\n    }\n\n    m_operation->run(m_model->getByIndex(connectionIndex), dbIndex);\n  } else {\n    m_operation->run();\n  }\n}\n\nvoid BulkOperations::Manager::getAffectedKeys() {\n  if (!hasOperation()) return;\n\n  m_operation->getAffectedKeys([this](QVariant r, QString e) {\n    if (!e.isEmpty()) {\n      emit error(e, \"\");\n      return;\n    }\n\n    emit affectedKeys(r);\n  });\n}\n\nQVariant BulkOperations::Manager::getTargetConnections() {\n  return QVariant(m_model->getConnections());\n}\n\nvoid BulkOperations::Manager::setOperationMetadata(const QVariantMap& meta) {\n  if (hasOperation()) m_operation->setMetadata(meta);\n}\n\nQString BulkOperations::Manager::operationName() const {\n  if (!hasOperation()) return QString();\n\n  return m_operation->getTypeName();\n}\n\nQString BulkOperations::Manager::connectionName() const {\n  if (!hasOperation()) return QString();\n\n  return m_operation->getConnection()->getConfig().name();\n}\n\nint BulkOperations::Manager::dbIndex() const {\n  if (!hasOperation()) return -1;\n\n  return m_operation->getDbIndex();\n}\n\nQString BulkOperations::Manager::keyPattern() const {\n  if (!hasOperation()) return QString();\n\n  return m_operation->getKeyPattern().pattern();\n}\n\nvoid BulkOperations::Manager::setKeyPattern(const QString& p) {\n  if (!hasOperation()) return;\n\n  m_operation->setKeyPattern(QRegExp(p, Qt::CaseSensitive, QRegExp::Wildcard));\n}\n\nint BulkOperations::Manager::operationProgress() const {\n  if (!hasOperation()) return -1;\n\n  return m_operation->currentProgress();\n}\n\nvoid BulkOperations::Manager::requestBulkOperation(\n    QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n    BulkOperations::Manager::Operation op, QRegExp keyPattern,\n    AbstractOperation::OperationCallback callback) {\n  if (hasOperation()) {\n    qWarning() << \"BulkOperationsManager already has bulk operation request\";\n    return;\n  }\n\n  auto callbackWrapper = [this, callback](QRegExp filter, long processed,\n                                          const QStringList& e) {\n    if (e.size() > 0) {\n      emit error(QCoreApplication::translate(\n                     \"RESP\", \"Failed to perform actions on %1 keys. \")\n                     .arg(e.size()),\n                 e.join(\"\\n\"));\n    } else {\n      emit operationFinished();\n    }\n\n    return callback(filter, processed, e);\n  };\n\n  if (op == Operation::DELETE_KEYS) {\n    m_operation = QSharedPointer<BulkOperations::AbstractOperation>(\n        new BulkOperations::DeleteOperation(connection, dbIndex,\n                                            callbackWrapper, keyPattern));\n  } else if (op == Operation::TTL) {\n    m_operation = QSharedPointer<BulkOperations::AbstractOperation>(\n        new BulkOperations::TtlOperation(connection, dbIndex, callbackWrapper,\n                                         keyPattern));\n  } else if (op == Operation::COPY_KEYS) {\n    m_operation = QSharedPointer<BulkOperations::AbstractOperation>(\n        new BulkOperations::CopyOperation(connection, dbIndex, callbackWrapper,\n                                          keyPattern));\n  } else if (op == Operation::IMPORT_RDB_KEYS) {\n    if (!m_python) {\n      qWarning() << \"Python is not ready yet\";\n      return;\n    }\n\n    m_operation = QSharedPointer<BulkOperations::AbstractOperation>(\n        new BulkOperations::RDBImportOperation(\n            connection, dbIndex, callbackWrapper, m_python, keyPattern));\n  }\n\n  QObject::connect(m_operation.data(),\n                   &BulkOperations::AbstractOperation::progress, this,\n                   [this](int) { emit operationProgressChanged(); });\n\n  emit operationNameChanged();\n  emit connectionNameChanged();\n  emit dbIndexChanged();\n  emit keyPatternChanged();\n  emit operationProgressChanged();\n  emit openDialog(m_operation->getTypeName());\n}\n"
  },
  {
    "path": "src/modules/bulk-operations/bulkoperationsmanager.h",
    "content": "#pragma once\n#include <qredisclient/response.h>\n#include <QObject>\n#include <QRegExp>\n#include <QSharedPointer>\n#include <QString>\n#include <QStringList>\n#include <QVariant>\n#include <functional>\n\n#include \"connections.h\"\n#include \"operations/abstractoperation.h\"\n\nclass QPython;\n\nnamespace BulkOperations {\n\nclass Manager : public QObject {\n  Q_OBJECT\n\n  Q_PROPERTY(\n      QString operationName READ operationName NOTIFY operationNameChanged)\n  Q_PROPERTY(\n      QString connectionName READ connectionName NOTIFY connectionNameChanged)\n  Q_PROPERTY(int dbIndex READ dbIndex NOTIFY dbIndexChanged)\n  Q_PROPERTY(QString keyPattern READ keyPattern WRITE setKeyPattern NOTIFY\n                 keyPatternChanged)\n  Q_PROPERTY(int operationProgress READ operationProgress NOTIFY\n                 operationProgressChanged)\n public:\n  enum class Operation {\n    DELETE_KEYS,\n    COPY_KEYS,\n    IMPORT_RDB_KEYS,\n    TTL,\n  };\n\n public:\n  Manager(QSharedPointer<ConnectionsModel> model);\n\n  void setPython(QSharedPointer<QPython> p);\n\n  Q_INVOKABLE bool hasOperation() const;\n  Q_INVOKABLE bool multiConnectionOperation() const;\n  Q_INVOKABLE bool clearOperation();\n  Q_INVOKABLE void runOperation(int targetConnection = -1, int targetDb = -1);\n  Q_INVOKABLE void getAffectedKeys();\n  Q_INVOKABLE QVariant getTargetConnections();\n\n  Q_INVOKABLE void setOperationMetadata(const QVariantMap& meta);\n\n  // Property getters\n  QString operationName() const;\n\n  QString connectionName() const;\n\n  int dbIndex() const;\n\n  QString keyPattern() const;\n  void setKeyPattern(const QString& p);\n\n  int operationProgress() const;\n\n signals:\n  void openDialog(const QString& operationName);\n  void affectedKeys(QVariant r);\n  void operationFinished();\n  void error(const QString& e, const QString& details);\n\n  // Property notifiers\n  void operationNameChanged();\n  void connectionNameChanged();\n  void dbIndexChanged();\n  void keyPatternChanged();\n  void operationProgressChanged();\n\n public slots:\n  void requestBulkOperation(QSharedPointer<RedisClient::Connection> connection,\n                            int dbIndex, Operation op, QRegExp keyPattern,\n                            AbstractOperation::OperationCallback callback);\n\n private:\n  QSharedPointer<AbstractOperation> m_operation;\n  QSharedPointer<ConnectionsModel> m_model;\n  QSharedPointer<QPython> m_python;\n};\n}  // namespace BulkOperations\n"
  },
  {
    "path": "src/modules/bulk-operations/connections.h",
    "content": "#pragma once\n#include <QSharedPointer>\n#include <QStringList>\n\nnamespace RedisClient {\nclass Connection;\n}\n\nnamespace BulkOperations {\n\nclass ConnectionsModel {\n public:\n  virtual QSharedPointer<RedisClient::Connection> getByIndex(int index) = 0;\n\n  virtual QStringList getConnections() = 0;\n};\n}  // namespace BulkOperations\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/abstractoperation.cpp",
    "content": "#include \"abstractoperation.h\"\n#include <qredisclient/utils/text.h>\n\nBulkOperations::AbstractOperation::AbstractOperation(\n    QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n    OperationCallback callback, QRegExp keyPattern)\n    : m_connection(connection),\n      m_dbIndex(dbIndex),\n      m_keyPattern(keyPattern),\n      m_currentState(State::READY),\n      m_progress(0),\n      m_callback(callback),\n      m_lastProgressNotification(0) {}\n\nvoid BulkOperations::AbstractOperation::getAffectedKeys(\n    std::function<void(QVariant, QString)> callback) {\n  auto processingCallback =\n      [this, callback](const RedisClient::Connection::RawKeysList& keys,\n                       const QString& err) {\n        if (!err.isEmpty()) {\n          return callback(QVariant(), err);\n        }\n\n        m_affectedKeys.clear();\n\n        QStringList keyNames;\n\n        for (const QByteArray &k : keys) {\n          m_affectedKeys.append(k);\n          keyNames.append(printableString(k, true));\n        }\n\n        return callback(QVariant(keyNames), \"\");\n      };\n\n  try {\n    if (!m_connection->connect(true)) {\n      return callback(QVariant(), QCoreApplication::translate(\n                                      \"RESP\", \"Cannot connect to redis-server\"));\n    }\n\n    if (m_connection->mode() == RedisClient::Connection::Mode::Cluster) {\n      m_connection->getClusterKeys(processingCallback, m_keyPattern.pattern());\n    } else {\n      m_connection->getDatabaseKeys(processingCallback, m_keyPattern.pattern(),\n                                    m_dbIndex);\n    }\n\n  } catch (const RedisClient::Connection::Exception& e) {\n    return callback(QVariant(), QString(e.what()));\n  }\n}\n\nvoid BulkOperations::AbstractOperation::run(\n    QSharedPointer<RedisClient::Connection> targetConnection,\n    int targetDbIndex) {\n  if (!isMetadataValid()) {\n    qWarning() << QString(\"Invalid metadata for %1\").arg(getTypeName());\n    return;\n  }\n\n  if (m_affectedKeys.size() > 0) {\n    performOperation(targetConnection, targetDbIndex);\n  } else {\n    getAffectedKeys([this, targetConnection, targetDbIndex](QVariant, QString) {\n      performOperation(targetConnection, targetDbIndex);\n    });\n  }\n}\n\nbool BulkOperations::AbstractOperation::isRunning() const {\n  return m_currentState == State::RUNNING;\n}\n\nQSharedPointer<RedisClient::Connection>\nBulkOperations::AbstractOperation::getConnection() {\n  return m_connection;\n}\n\nint BulkOperations::AbstractOperation::getDbIndex() const { return m_dbIndex; }\n\nQRegExp BulkOperations::AbstractOperation::getKeyPattern() const {\n  return m_keyPattern;\n}\n\nvoid BulkOperations::AbstractOperation::setKeyPattern(const QRegExp p) {\n  m_keyPattern = p;\n}\n\nint BulkOperations::AbstractOperation::currentProgress() const {\n  return m_progress;\n}\n\nvoid BulkOperations::AbstractOperation::setMetadata(const QVariantMap& meta) {\n  m_metadata = meta;\n}\n\nvoid BulkOperations::AbstractOperation::incrementProgress() {\n  QMutexLocker l(&m_processedKeysMutex);\n  m_progress++;\n\n  if (QDateTime::currentMSecsSinceEpoch() - m_lastProgressNotification >=\n      1000) {\n    qDebug() << \"Notify UI about progress\";\n    emit progress(m_progress);\n    m_lastProgressNotification = QDateTime::currentMSecsSinceEpoch();\n  }\n}\n\nvoid BulkOperations::AbstractOperation::processError(const QString& err) {\n  {\n      QMutexLocker l(&m_errorsMutex);\n      m_errors.append(m_errorMessagePrefix + err);\n  }\n  m_callback(m_keyPattern, m_progress, m_errors);\n}\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/abstractoperation.h",
    "content": "#pragma once\n#include <QObject>\n#include <QRegExp>\n#include <QSharedPointer>\n\n#include <qredisclient/connection.h>\n\nnamespace BulkOperations {\n\nclass AbstractOperation : public QObject {\n  Q_OBJECT\n\n public:\n  enum class State { READY, RUNNING, FINISHED };\n\n  typedef std::function<void(QRegExp affectedKeysFilter, long processed,\n                             const QStringList& errors)>\n      OperationCallback;\n\n public:\n  AbstractOperation(QSharedPointer<RedisClient::Connection> connection,\n                    int dbIndex, OperationCallback callback,\n                    QRegExp keyPattern = QRegExp(\"*\", Qt::CaseSensitive,\n                                                 QRegExp::Wildcard));\n\n  virtual ~AbstractOperation() {}\n\n  virtual void getAffectedKeys(std::function<void(QVariant, QString)> callback);\n\n  virtual void run(QSharedPointer<RedisClient::Connection> targetConnection =\n                       QSharedPointer<RedisClient::Connection>(),\n                   int targetDbIndex = 0);\n\n  virtual QString getTypeName() const = 0;\n\n  virtual bool multiConnectionOperation() const = 0;\n\n  bool isRunning() const;\n\n  QSharedPointer<RedisClient::Connection> getConnection();\n\n  int getDbIndex() const;\n\n  QRegExp getKeyPattern() const;\n\n  void setKeyPattern(const QRegExp p);\n\n  int currentProgress() const;\n\n  void setMetadata(const QVariantMap& meta);\n\n signals:\n  void progress(int processed);\n\n protected:\n  virtual bool isMetadataValid() const { return true; }\n\n  virtual void performOperation(\n      QSharedPointer<RedisClient::Connection> targetConnection,\n      int targetDbIndex) = 0;\n\n  void incrementProgress();\n\n  void processError(const QString& err);\n\n protected:\n  QSharedPointer<RedisClient::Connection> m_connection;\n  int m_dbIndex;\n  QRegExp m_keyPattern;\n  State m_currentState;\n  int m_progress;\n  QList<QByteArray> m_affectedKeys;\n  QList<QByteArray> m_keysWithErrors;\n  QVariantMap m_metadata;\n  OperationCallback m_callback;\n  QStringList m_errors;\n  QMutex m_errorsMutex;\n  QMutex m_processedKeysMutex;\n  qint64 m_lastProgressNotification;\n  QString m_errorMessagePrefix;\n};\n}  // namespace BulkOperations\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/copyoperation.cpp",
    "content": "#include \"copyoperation.h\"\n\n#include <QtConcurrent>\n\n#define RESTORE_BUFFER_LIMIT 100\n\nBulkOperations::CopyOperation::CopyOperation(\n    QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n    OperationCallback callback, QRegExp keyPattern)\n    : BulkOperations::AbstractOperation(connection, dbIndex, callback,\n                                        keyPattern) {\n  m_errorMessagePrefix = QCoreApplication::translate(\"RESP\", \"Cannot copy key \");\n}\n\nvoid BulkOperations::CopyOperation::performOperation(\n    QSharedPointer<RedisClient::Connection> targetConnection,\n    int targetDbIndex) {\n  m_progress = 0;\n  m_dumpedKeys = 0;\n  m_errors.clear();\n\n  auto returnResults = [this]() {\n    m_callback(m_keyPattern, m_progress, m_errors);\n  };\n\n  if (m_affectedKeys.size() == 0) {\n    return returnResults();\n  }\n\n  QByteArray ttl =\n      QString::number(m_metadata[\"ttl\"].toLongLong() * 1000).toUtf8();\n  QByteArray replace = m_metadata[\"replace\"].toString().toUpper().toUtf8();\n\n  auto processKeyDumps = [this, returnResults, ttl, replace, targetConnection,\n                          targetDbIndex](const RedisClient::Response& r,\n                                         QString err) {\n    if (!err.isEmpty()) {\n      return processError(err);\n    }\n\n    {\n      QMutexLocker l(&m_processedKeysMutex);\n      QVariant incrResult = r.value();\n\n      auto getRestoreCmd = [this, r, replace, ttl](const QByteArray& dump) {\n        QList<QByteArray> restoreCmd{\n            \"RESTORE\", m_affectedKeys[m_dumpedKeys], ttl, dump};\n        if (!replace.isEmpty()) {\n          restoreCmd.append(replace);\n        }\n        return restoreCmd;\n      };\n\n      if (incrResult.canConvert(QVariant::ByteArray)) {\n        m_restoreBuffer.append(getRestoreCmd(incrResult.toByteArray()));\n        m_dumpedKeys++;\n      } else if (incrResult.canConvert(QVariant::List)) {\n        auto responses = incrResult.toList();\n\n        for (auto resp : qAsConst(responses)) {\n          m_restoreBuffer.append(getRestoreCmd(resp.toByteArray()));\n          m_dumpedKeys++;\n        }\n      }\n    }\n\n    if (m_restoreBuffer.size() > RESTORE_BUFFER_LIMIT ||\n        m_dumpedKeys == m_affectedKeys.size()) {\n      int batchSize = m_restoreBuffer.size();\n\n      targetConnection->pipelinedCmd(\n          m_restoreBuffer, this, targetDbIndex,\n          [this, returnResults, batchSize](const RedisClient::Response& r, QString err) {\n            if (!err.isEmpty() || r.isErrorMessage()) {\n              return processError(err.isEmpty()? r.value().toByteArray() : err);\n            }\n            {\n              QMutexLocker l(&m_processedKeysMutex);\n              m_progress += batchSize;\n              emit progress(m_progress);\n            }\n\n            if (m_progress >= m_affectedKeys.size()) {\n              returnResults();\n            }\n          }, false);\n      m_restoreBuffer.clear();\n    }\n  };\n\n  auto processKeys = [this, processKeyDumps]() {\n    QList<QList<QByteArray>> rawCmds;\n\n    for (const QByteArray &k : qAsConst(m_affectedKeys)) {\n      rawCmds.append({\"DUMP\", k});\n    }\n\n    m_connection->pipelinedCmd(rawCmds, this, -1, processKeyDumps, true);\n  };\n\n  auto verifySourceConnection = [this, processKeys, targetConnection]() {\n    m_connection->cmd(\n        {\"ping\"}, this, m_dbIndex,\n        [processKeys, this](const RedisClient::Response& r) {\n          if (r.isErrorMessage()) {\n            return processError(\n                QCoreApplication::translate(\"RESP\", \"Source connection error\"));\n          }\n          QtConcurrent::run(processKeys);\n        },\n        [this](const QString& err) { processError(err); });\n  };\n\n  targetConnection->cmd(\n      {\"ping\"}, this, targetDbIndex,\n      [verifySourceConnection, this](const RedisClient::Response& r) {\n        if (r.isErrorMessage()) {\n          return processError(\n              QCoreApplication::translate(\"RESP\", \"Target connection error\"));\n        }\n\n        verifySourceConnection();\n      },\n      [this](const QString& err) { processError(err); });\n}\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/copyoperation.h",
    "content": "#pragma once\n#include <asyncfuture.h>\n#include <QObject>\n#include <QRegExp>\n#include <QSharedPointer>\n#include \"abstractoperation.h\"\n\nnamespace BulkOperations {\n\nclass CopyOperation : public AbstractOperation {\n  Q_OBJECT\n public:\n  CopyOperation(QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n                OperationCallback callback,\n                QRegExp keyPattern = QRegExp(\"*\", Qt::CaseSensitive,\n                                             QRegExp::Wildcard));\n\n  QString getTypeName() const override { return QString(\"copy_keys\"); }\n\n  bool multiConnectionOperation() const override { return true; }\n\n  bool isMetadataValid() const override {\n    return m_metadata.contains(\"ttl\") && m_metadata.contains(\"replace\");\n  }\n\n protected:\n  void performOperation(\n      QSharedPointer<RedisClient::Connection> targetConnection,\n      int targetDbIndex) override;\n\n private:\n   QList<QList<QByteArray>> m_restoreBuffer;\n   int m_dumpedKeys;\n\n};\n}  // namespace BulkOperations\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/deleteoperation.cpp",
    "content": "#include \"deleteoperation.h\"\n\n#include <QtConcurrent>\n\nBulkOperations::DeleteOperation::DeleteOperation(\n    QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n    OperationCallback callback, QRegExp keyPattern)\n    : BulkOperations::AbstractOperation(connection, dbIndex, callback,\n                                        keyPattern) {\n  m_errorMessagePrefix =\n      QCoreApplication::translate(\"RESP\", \"Cannot remove key \");\n}\n\nvoid BulkOperations::DeleteOperation::performOperation(\n    QSharedPointer<RedisClient::Connection>, int) {\n  m_progress = 0;\n  m_errors.clear();\n\n  auto returnResults = [this]() {\n    qDebug() << \"Processed keys: \" << m_progress;\n    m_callback(m_keyPattern, m_progress, m_errors);\n  };\n\n  if (m_affectedKeys.size() == 0) {\n    return returnResults();\n  }\n\n  AsyncFuture::observe(m_connection->isCommandSupported({\"UNLINK\"}))\n      .subscribe([this, returnResults](bool supportUnlink) {\n        QByteArray removalCmd{\"DEL\"};\n\n        if (supportUnlink) {\n          removalCmd = \"UNLINK\";\n        }\n\n        QtConcurrent::run(this, &DeleteOperation::deleteKeys, m_affectedKeys,\n                          removalCmd, [this, removalCmd, returnResults]() {\n                            // Retry on keys with errors\n                            if (m_keysWithErrors.size() > 0) {\n                              m_errors.clear();\n                              deleteKeys(m_keysWithErrors,\n                                         removalCmd, returnResults);\n                            } else {\n                              returnResults();\n                            }\n                          });\n      });\n}\n\nvoid BulkOperations::DeleteOperation::deleteKeys(\n    const QList<QByteArray> &keys, const QByteArray &rmCmd,\n    std::function<void()> callback) {\n  QList<QList<QByteArray>> rawCmds;\n\n  for (const QByteArray& k : keys) {\n    rawCmds.append({rmCmd, k});\n  }\n\n  int batchSize = m_connection->pipelineCommandsLimit();\n  int expectedResponses = rawCmds.size();\n\n  m_connection->pipelinedCmd(\n      rawCmds, this, m_dbIndex,\n      [this, expectedResponses, callback, batchSize](\n          const RedisClient::Response &r, QString err) {\n        if (!err.isEmpty() || r.isErrorMessage()) {\n          return processError(err.isEmpty() ? r.value().toByteArray() : err);\n        }\n\n        {\n          QMutexLocker l(&m_processedKeysMutex);\n          m_progress += batchSize;\n\n          emit progress(m_progress);\n        }\n\n        if (m_progress >= expectedResponses) {\n          callback();\n        }\n      },\n      false);\n}\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/deleteoperation.h",
    "content": "#pragma once\n#include <asyncfuture.h>\n#include <QObject>\n#include <QRegExp>\n#include <QSharedPointer>\n#include \"abstractoperation.h\"\n\nnamespace BulkOperations {\n\nclass DeleteOperation : public AbstractOperation {\n  Q_OBJECT\n public:\n  DeleteOperation(QSharedPointer<RedisClient::Connection> connection,\n                  int dbIndex, OperationCallback callback,\n                  QRegExp keyPattern = QRegExp(\"*\", Qt::CaseSensitive,\n                                               QRegExp::Wildcard));\n\n  QString getTypeName() const override { return QString(\"delete_keys\"); }\n\n  bool multiConnectionOperation() const override { return false; }\n\n protected:\n  void performOperation(\n      QSharedPointer<RedisClient::Connection> targetConnection,\n      int targetDbIndex) override;\n\n  void deleteKeys(const QList<QByteArray> &keys,\n                  const QByteArray& rmCmd,\n                  std::function<void()> callback);\n};\n}  // namespace BulkOperations\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/rdbimport.cpp",
    "content": "#include \"rdbimport.h\"\n\n#include <qpython.h>\n#include <qredisclient/utils/text.h>\n\n#include <QFileInfo>\n#include <QtConcurrent>\n\nBulkOperations::RDBImportOperation::RDBImportOperation(\n    QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n    OperationCallback callback, QSharedPointer<QPython> p, QRegExp keyPattern)\n    : BulkOperations::AbstractOperation(connection, dbIndex, callback,\n                                        keyPattern),\n      m_python(p) {\n  m_python->importModule_sync(\"rdb\");\n  m_errorMessagePrefix =\n      QCoreApplication::translate(\"RESP\", \"Cannot execute command \");\n}\n\nvoid BulkOperations::RDBImportOperation::getAffectedKeys(\n    std::function<void(QVariant, QString)> callback) {\n\n  m_keyPattern.setPatternSyntax(QRegExp::RegExp2);\n\n  if (!m_keyPattern.isValid()) {\n    return callback(QVariant(), QCoreApplication::translate(\n                                    \"RESP\", \"Invalid regexp for keys filter.\"));\n  }\n\n  m_python->call_native(\n      \"rdb.rdb_list_keys\",\n      QVariantList{m_metadata[\"path\"].toString(), m_metadata[\"db\"].toInt(),\n                   m_keyPattern.pattern()},\n      [callback, this](QVariant v) {\n        m_affectedKeys.clear();\n\n        if (v.isNull()) {\n          return callback(QVariant(),\n                          QCoreApplication::translate(\n                              \"RESP\", \"Cannot get the list of affected keys\"));\n        }\n\n        QVariantList keys = v.toList();\n        QStringList keyNames;\n\n        for (const QVariant &k : qAsConst(keys)) {\n          m_affectedKeys.append(k.toByteArray());\n          keyNames.append(printableString(k.toByteArray(), true));\n        }\n\n        return callback(QVariant(keyNames), \"\");\n      });\n}\n\nbool BulkOperations::RDBImportOperation::isMetadataValid() const {\n  return m_metadata.contains(\"db\") && m_metadata.contains(\"path\") &&\n         QFileInfo::exists(m_metadata[\"path\"].toString());\n}\n\nQList<QByteArray> convertToByteArray(QVariant v) {\n  QVariantList l = v.toList();\n\n  QList<QByteArray> result;\n\n  for (const QVariant &b : qAsConst(l)) {\n    result.append(b.toByteArray());\n  }\n\n  return result;\n}\n\nvoid BulkOperations::RDBImportOperation::performOperation(\n    QSharedPointer<RedisClient::Connection>, int) {\n  m_progress = 0;\n  m_errors.clear();\n\n  auto returnResults = [this]() {\n    m_callback(m_keyPattern, m_progress, m_errors);\n  };\n\n  if (m_affectedKeys.size() == 0) {\n    return returnResults();\n  }\n\n  auto processCommands = [this, returnResults](const QVariantList& commands) {\n    QList<QList<QByteArray>> rawCmds;\n\n    for (const QVariant &cmd : commands) {\n      auto rawCmd = convertToByteArray(cmd);\n\n      if (rawCmd.at(0).toLower() == QByteArray(\"select\")) {\n        continue;\n      }\n\n      rawCmds.append(rawCmd);\n    }\n\n    int batchSize = m_connection->pipelineCommandsLimit();\n    int expectedResponses = rawCmds.size();\n\n    m_connection->pipelinedCmd(\n        rawCmds, this, m_dbIndex,\n        [this, returnResults, expectedResponses, batchSize](const RedisClient::Response& r,\n                                                 const QString& err) {\n          if (!err.isEmpty() || r.isErrorMessage()) {\n            return processError(err.isEmpty()? r.value().toByteArray() : err);\n          }\n\n          {\n            QMutexLocker l(&m_processedKeysMutex);            \n            m_progress += batchSize;\n            emit progress(m_progress);\n          }\n\n          if (m_progress >= expectedResponses) {\n            returnResults();\n          }\n        }, false);\n  };\n\n  m_python->call_native(\n      \"rdb.rdb_export_as_commands\",\n      QVariantList{m_metadata[\"path\"].toString(), m_metadata[\"db\"].toInt(),\n                   m_keyPattern.pattern()},\n      [processCommands, this](QVariant v) {\n        QVariantList commands = v.toList();\n\n        m_connection->cmd(\n            {\"ping\"}, this, m_dbIndex,\n            [processCommands, commands, this](const RedisClient::Response& r) {\n              if (r.isErrorMessage()) {\n                return processError(QCoreApplication::translate(\n                    \"RESP\", \"Target connection error\"));\n              }\n              QtConcurrent::run(processCommands, commands);\n            },\n            [this](const QString& err) { processError(err); });\n      });\n}\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/rdbimport.h",
    "content": "#pragma once\n#include <asyncfuture.h>\n#include <QObject>\n#include <QRegExp>\n#include <QSharedPointer>\n#include \"abstractoperation.h\"\n\nclass QPython;\n\nnamespace BulkOperations {\n\nclass RDBImportOperation : public AbstractOperation {\n  Q_OBJECT\n public:\n  RDBImportOperation(QSharedPointer<RedisClient::Connection> connection,\n                     int dbIndex, OperationCallback callback,\n                     QSharedPointer<QPython> p,\n                     QRegExp keyPattern = QRegExp(\"*\", Qt::CaseSensitive,\n                                                  QRegExp::Wildcard));\n\n  QString getTypeName() const override { return QString(\"rdb_import\"); }\n\n  bool multiConnectionOperation() const override { return false; }\n\n  void getAffectedKeys(\n      std::function<void(QVariant, QString)> callback) override;\n\n  bool isMetadataValid() const override;\n\n protected:\n  void performOperation(\n      QSharedPointer<RedisClient::Connection> targetConnection,\n      int targetDbIndex) override;\n\n private:\n  QSharedPointer<QPython> m_python;\n};\n}  // namespace BulkOperations\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/ttloperation.cpp",
    "content": "#include \"ttloperation.h\"\n\n#include <QtConcurrent>\n\nBulkOperations::TtlOperation::TtlOperation(\n    QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n    OperationCallback callback, QRegExp keyPattern)\n    : BulkOperations::AbstractOperation(connection, dbIndex, callback,\n                                        keyPattern) {\n  m_errorMessagePrefix =\n      QCoreApplication::translate(\"RESP\", \"Cannot set TTL for key \");\n}\n\nvoid BulkOperations::TtlOperation::performOperation(\n    QSharedPointer<RedisClient::Connection>, int) {\n  m_progress = 0;\n  m_errors.clear();\n\n  auto returnResults = [this]() {\n    m_callback(m_keyPattern, m_progress, m_errors);\n  };\n\n  if (m_affectedKeys.size() == 0) {\n    return returnResults();\n  }\n\n  QByteArray ttl = m_metadata[\"ttl\"].toString().toUtf8();\n\n  QtConcurrent::run(this, &TtlOperation::setTtl, m_affectedKeys, ttl,\n                    [this, ttl, returnResults]() {\n                      // Retry on keys with errors\n                      if (m_keysWithErrors.size() > 0) {\n                        m_errors.clear();\n                        setTtl(m_keysWithErrors, ttl,\n                               returnResults);\n                      } else {\n                        returnResults();\n                      }\n                    });\n}\n\nvoid BulkOperations::TtlOperation::setTtl(const QList<QByteArray>& keys,\n                                          const QByteArray& ttl,\n                                          std::function<void()> callback) {\n  QList<QList<QByteArray>> rawCmds;\n\n  for (const QByteArray& k : keys) {\n    rawCmds.append({\"EXPIRE\", k, ttl});\n  }\n\n  int batchSize = m_connection->pipelineCommandsLimit();\n  int expectedResponses = rawCmds.size();\n\n  m_connection->pipelinedCmd(\n      rawCmds, this, -1,\n      [this, expectedResponses, callback, batchSize](\n          const RedisClient::Response& r, QString err) {\n        if (!err.isEmpty() || r.isErrorMessage()) {\n          return processError(err.isEmpty() ? r.value().toByteArray() : err);\n        }\n\n        {\n          QMutexLocker l(&m_processedKeysMutex);\n          m_progress += batchSize;\n          emit progress(m_progress);\n        }\n\n        if (m_progress >= expectedResponses) {\n          callback();\n        }\n      },\n      false);\n}\n"
  },
  {
    "path": "src/modules/bulk-operations/operations/ttloperation.h",
    "content": "#pragma once\n#include <asyncfuture.h>\n#include <QObject>\n#include <QRegExp>\n#include <QSharedPointer>\n#include \"abstractoperation.h\"\n\nnamespace BulkOperations {\n\nclass TtlOperation : public AbstractOperation {\n  Q_OBJECT\n public:\n  TtlOperation(QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n               OperationCallback callback,\n               QRegExp keyPattern = QRegExp(\"*\", Qt::CaseSensitive,\n                                            QRegExp::Wildcard));\n\n  QString getTypeName() const override { return QString(\"ttl\"); }\n\n  bool multiConnectionOperation() const override { return false; }\n\n  bool isMetadataValid() const override { return m_metadata.contains(\"ttl\"); }\n\n protected:\n  void performOperation(\n      QSharedPointer<RedisClient::Connection> targetConnection,\n      int targetDbIndex) override;\n\n\n  void setTtl(const QList<QByteArray> &keys, const QByteArray &ttl, std::function<void()> callback);\n};\n}  // namespace BulkOperations\n"
  },
  {
    "path": "src/modules/common/baselistmodel.cpp",
    "content": "#include \"baselistmodel.h\"\n\nBaseListModel::BaseListModel(QObject *parent)\n    : QAbstractListModel(parent)\n{\n\n}\n\nQVariantMap BaseListModel::getRowRaw(int row)\n{\n    QHash<int,QByteArray> names = roleNames();    \n    QHashIterator<int, QByteArray> i(names);\n    QVariantMap res;\n\n    while (i.hasNext()) {\n        i.next();\n\n        if (i.value() == \"display\")\n            continue;\n\n        QModelIndex idx = index(row);\n        QVariant d = data(idx, i.key());\n        res[i.value()] = d;\n    }\n    return res;\n}\n"
  },
  {
    "path": "src/modules/common/baselistmodel.h",
    "content": "#pragma once\n#include <QAbstractListModel>\n\nclass BaseListModel : public QAbstractListModel\n{\n    Q_OBJECT\n\n    public:\n        BaseListModel(QObject *parent = Q_NULLPTR);\n        virtual ~BaseListModel() {}\n\n    protected:\n        QVariantMap getRowRaw(int row);\n\n        inline bool isIndexValid(const QModelIndex &index) const\n        {\n            return 0 <= index.row() && index.row() < rowCount();\n        }\n\n        inline bool isRowIndexValid(int row) const\n        {\n            return 0 <= row && row < rowCount();\n        }\n};\n"
  },
  {
    "path": "src/modules/common/callbackwithowner.h",
    "content": "#pragma once\n#include <QDebug>\n#include <functional>\n#include <utility>\n#include <QWeakPointer>\n#include <QSharedPointer>\n\n\ntemplate <typename Object, typename ...Args>\nclass CallbackWithOwner\n{\npublic:\n  CallbackWithOwner(QWeakPointer<Object> owner, std::function<void(Args...)> c)\n    : m_owner(owner), m_callback(c)\n  {\n  }\n\n\n  void call(Args... args)\n  {\n      if (!isValid()) {\n          qDebug() << \"Callback owner was destroyed\";\n          return;\n      }\n\n      return m_callback(args...);\n  }\n\n  bool isValid()\n  {\n    auto owner = m_owner.toStrongRef();\n    return !owner.isNull();\n  }\n\n  std::function<void(Args...)> rawCallback()\n  {\n      return m_callback;\n  }\n\nprivate:\n  QWeakPointer<Object> m_owner;\n  std::function<void(Args...)> m_callback;\n};\n"
  },
  {
    "path": "src/modules/common/sortfilterproxymodel.cpp",
    "content": "#include \"sortfilterproxymodel.h\"\n\n#include \"sortfilterproxymodel.h\"\n#include <QtDebug>\n#include <QtQml>\n\nSortFilterProxyModel::SortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent), m_complete(false)\n{\n}\n\nQObject *SortFilterProxyModel::source() const\n{\n    return sourceModel();\n}\n\nvoid SortFilterProxyModel::setSource(QObject *source)\n{    \n    auto m = qobject_cast<QAbstractItemModel *>(source);\n\n    if (!m)\n        return;\n\n    setSourceModel(m);\n}\n\nQByteArray SortFilterProxyModel::sortRole() const\n{\n    return m_sortRole;\n}\n\nvoid SortFilterProxyModel::setSortRole(const QByteArray &role)\n{\n    if (m_sortRole != role) {\n        m_sortRole = role;\n        if (m_complete)\n            QSortFilterProxyModel::setSortRole(roleKey(role));\n    }\n}\n\nvoid SortFilterProxyModel::setSortOrder(Qt::SortOrder order)\n{\n    QSortFilterProxyModel::sort(0, order);\n}\n\nQByteArray SortFilterProxyModel::filterRole() const\n{\n    return m_filterRole;\n}\n\nvoid SortFilterProxyModel::setFilterRole(const QByteArray &role)\n{\n    if (m_filterRole != role) {\n        m_filterRole = role;\n        if (m_complete)\n            QSortFilterProxyModel::setFilterRole(roleKey(role));\n    }\n}\n\nQString SortFilterProxyModel::filterString() const\n{\n    return filterRegExp().pattern();\n}\n\nvoid SortFilterProxyModel::setFilterString(const QString &filter)\n{\n    setFilterRegExp(QRegExp(filter, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax())));\n    emit filterStringChanged();\n}\n\nSortFilterProxyModel::FilterSyntax SortFilterProxyModel::filterSyntax() const\n{\n    return static_cast<FilterSyntax>(filterRegExp().patternSyntax());\n}\n\nvoid SortFilterProxyModel::setFilterSyntax(SortFilterProxyModel::FilterSyntax syntax)\n{\n    setFilterRegExp(QRegExp(filterString(), filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(syntax)));\n}\n\nvoid SortFilterProxyModel::classBegin()\n{\n}\n\nvoid SortFilterProxyModel::componentComplete()\n{\n    m_complete = true;\n    if (!m_sortRole.isEmpty())\n        QSortFilterProxyModel::setSortRole(roleKey(m_sortRole));\n    if (!m_filterRole.isEmpty())\n        QSortFilterProxyModel::setFilterRole(roleKey(m_filterRole));\n}\n\nint SortFilterProxyModel::getOriginalRowIndex(int i)\n{\n    QModelIndex proxyIndex = index(i, 0);\n    return mapToSource(proxyIndex).row();\n}\n\nint SortFilterProxyModel::roleKey(const QByteArray &role) const\n{\n    QHash<int, QByteArray> roles = roleNames();\n    QHashIterator<int, QByteArray> it(roles);\n    while (it.hasNext()) {\n        it.next();\n        if (it.value() == role)\n            return it.key();\n    }\n    return -1;\n}\n"
  },
  {
    "path": "src/modules/common/sortfilterproxymodel.h",
    "content": "#pragma once\n\n#include <QtCore/qsortfilterproxymodel.h>\n#include <QtQml/qqmlparserstatus.h>\n\nclass SortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus\n{\n    Q_OBJECT\n    Q_INTERFACES(QQmlParserStatus)\n\n    Q_PROPERTY(QObject *source READ source WRITE setSource)\n\n    Q_PROPERTY(QByteArray sortRole READ sortRole WRITE setSortRole)\n    Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder)\n\n    Q_PROPERTY(QByteArray filterRole READ filterRole WRITE setFilterRole)\n    Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged)\n    Q_PROPERTY(FilterSyntax filterSyntax READ filterSyntax WRITE setFilterSyntax)\n\n    Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn)\n\n    Q_ENUMS(FilterSyntax)\n\npublic:\n    explicit SortFilterProxyModel(QObject *parent = 0);\n\n    QObject *source() const;\n    void setSource(QObject *source);\n\n    QByteArray sortRole() const;\n    Q_INVOKABLE void setSortRole(const QByteArray &role);\n\n    Q_INVOKABLE void setSortOrder(Qt::SortOrder order);\n\n    QByteArray filterRole() const;\n    void setFilterRole(const QByteArray &role);\n\n    QString filterString() const;\n    void setFilterString(const QString &filter);\n\n    enum FilterSyntax {\n        RegExp,\n        Wildcard,\n        FixedString\n    };\n\n    FilterSyntax filterSyntax() const;\n    void setFilterSyntax(FilterSyntax syntax);\n\n    void classBegin();\n    void componentComplete();\n\n    Q_INVOKABLE int getOriginalRowIndex(int i);    \n\nsignals:\n    void filterStringChanged();\n\nprotected:\n    int roleKey(const QByteArray &role) const;\n\nprivate:\n    bool m_complete;\n    QByteArray m_sortRole;\n    QByteArray m_filterRole;\n};\n"
  },
  {
    "path": "src/modules/common/tabmodel.cpp",
    "content": "#include \"tabmodel.h\"\n#include <QtConcurrent>\n\nTabModel::TabModel(QSharedPointer<RedisClient::Connection> connection,\n                   int dbIndex)\n    : m_dbIndex(dbIndex) {\n  m_connection = connection->clone();\n}\n\nTabModel::~TabModel() {\n  QtConcurrent::run(\n      [](QSharedPointer<RedisClient::Connection> connection) {\n        connection->disconnect();\n      },\n      m_connection);\n\n  m_connection.clear();\n}\n\nvoid TabModel::init() {\n  auto weekPointer = sharedFromThis().toWeakRef();\n\n  m_connection->callAfterConnect([this, weekPointer](const QString& err) {\n    if (!weekPointer) {\n      return;\n    }\n\n    if (!err.isEmpty()) {\n        emit error(err);\n        return;\n    }\n\n    if (m_dbIndex) {\n      m_connection->command({\"PING\"}, m_dbIndex);\n    }\n\n    emit initialized();\n  });\n\n  try {\n    m_connection->connect(false);\n  } catch (RedisClient::Connection::Exception&) {\n    emit error(QCoreApplication::translate(\n        \"RESP\", \"Invalid Connection. Check connection settings.\"));\n    return;\n  }\n}\n\nQSharedPointer<RedisClient::Connection> TabModel::getConnection() const {\n  return m_connection;\n}\n"
  },
  {
    "path": "src/modules/common/tabmodel.h",
    "content": "#pragma once\n#include <qredisclient/connection.h>\n#include <QEnableSharedFromThis>\n#include <QObject>\n\nclass TabModel : public QObject, public QEnableSharedFromThis<TabModel> {\n  Q_OBJECT\n\n public:\n  TabModel(QSharedPointer<RedisClient::Connection> connection, int dbIndex);\n\n  virtual ~TabModel();\n\n  virtual QString getName() const = 0;\n\n  Q_INVOKABLE virtual void init();\n\n  virtual QSharedPointer<RedisClient::Connection> getConnection() const;\n\n signals:\n  void error(const QString& error);\n\n  void initialized();\n\n protected:\n  QSharedPointer<RedisClient::Connection> m_connection;\n  uint m_dbIndex;\n};\n"
  },
  {
    "path": "src/modules/common/tabviewmodel.cpp",
    "content": "#include \"tabviewmodel.h\"\n#include <QQmlEngine>\n\nTabViewModel::TabViewModel(const ModelFactory &modelFactory)\n    : m_modelFactory(modelFactory) {}\n\nQModelIndex TabViewModel::index(int row, int, const QModelIndex &) const {\n  return createIndex(row, 0);\n}\n\nint TabViewModel::rowCount(const QModelIndex &parent) const {\n  Q_UNUSED(parent)\n  return m_models.count();\n}\n\nQVariant TabViewModel::data(const QModelIndex &index, int role) const {\n  if (!isIndexValid(index)) return QVariant();\n\n  QSharedPointer<TabModel> model = m_models.at(index.row());\n\n  if (model.isNull()) return QVariant();\n\n  switch (role) {\n    case tabIndex:\n      return index.row();\n    case tabName:\n      return model->getName();\n    case tabModel:\n      QObject *modelPtr =\n          static_cast<QObject *>(m_models.at(index.row()).data());\n      QQmlEngine::setObjectOwnership(modelPtr, QQmlEngine::CppOwnership);\n\n      return QVariant::fromValue(modelPtr);\n  }\n\n  return QVariant();\n}\n\nQHash<int, QByteArray> TabViewModel::roleNames() const {\n  QHash<int, QByteArray> roles;\n  roles[tabIndex] = \"tabIndex\";\n  roles[tabName] = \"tabName\";\n  roles[tabModel] = \"tabModel\";\n  return roles;\n}\n\nvoid TabViewModel::closeTab(int i) {\n  if (!isIndexValid(index(i, 0))) return;\n\n  beginRemoveRows(QModelIndex(), i, i);\n  m_models.at(i)->disconnect();\n  m_models.removeAt(i);\n  endRemoveRows();\n}\n\nint TabViewModel::tabsCount() const { return m_models.count(); }\n\nvoid TabViewModel::openTab(QSharedPointer<RedisClient::Connection> connection,\n                           int dbIndex, bool inNewTab,\n                           QList<QByteArray> initCmd) {\n  if (inNewTab) {\n    beginInsertRows(QModelIndex(), m_models.count(), m_models.count());\n    m_models.append(m_modelFactory(connection, dbIndex, initCmd));\n    emit changeCurrentTab(m_models.size() - 1);\n    endInsertRows();\n  } else {\n    bool found = false;\n\n    for (int index = 0; 0 <= index && index < m_models.size(); index++) {\n      auto model = m_models.at(index);\n\n      if (model->getConnection()->getConfig().id() ==\n          connection->getConfig().id()) {\n        found = true;\n        emit changeCurrentTab(index);\n        break;\n      }\n    }\n\n    if (!found) {\n        return openTab(connection, dbIndex, true, initCmd);\n    }\n\n  }\n}\n\nvoid TabViewModel::closeAllTabsWithConnection(\n    QSharedPointer<RedisClient::Connection> connection) {\n  for (int index = 0; 0 <= index && index < m_models.size(); index++) {\n    auto model = m_models.at(index);    \n\n    if (model->getConnection()->getConfig().id() == connection->getConfig().id()) {\n      beginRemoveRows(QModelIndex(), index, index);\n      m_models.removeAt(index);\n      endRemoveRows();\n      index--;\n    }\n  }\n}\n\nbool TabViewModel::isIndexValid(const QModelIndex &index) const {\n  return 0 <= index.row() && index.row() < rowCount();\n}\n"
  },
  {
    "path": "src/modules/common/tabviewmodel.h",
    "content": "#pragma once\n#include <QAbstractListModel>\n#include <functional>\n#include \"tabmodel.h\"\n\nclass TabViewModel : public QAbstractListModel {\n  Q_OBJECT\n\n public:\n  enum Roles {\n    tabName = Qt::UserRole + 1,\n    tabIndex,\n    tabModel,\n  };\n\n  typedef std::function<QSharedPointer<TabModel>(\n      QSharedPointer<RedisClient::Connection>, int dbIndex, QList<QByteArray> initCmd)>\n      ModelFactory;\n\n public:\n  TabViewModel(const ModelFactory& modelFactory);\n\n  QModelIndex index(int row, int column = 0,\n                    const QModelIndex& parent = QModelIndex()) const override;\n\n  int rowCount(const QModelIndex& parent = QModelIndex()) const override;\n\n  QVariant data(const QModelIndex& index,\n                int role = Qt::DisplayRole) const override;\n\n  QHash<int, QByteArray> roleNames() const override;\n\n public:  // methods exported to QML\n  Q_INVOKABLE void closeTab(int i);\n\n  Q_INVOKABLE int tabsCount() const;\n\n signals:\n  void changeCurrentTab(int i);\n\n public slots:\n  virtual void openTab(QSharedPointer<RedisClient::Connection> connection,\n                       int dbIndex=0, bool inNewTab=true,\n                       QList<QByteArray> initCmd = QList<QByteArray>());\n\n  void closeAllTabsWithConnection(\n      QSharedPointer<RedisClient::Connection> connection);\n\n private:\n  QList<QSharedPointer<TabModel>> m_models;\n  ModelFactory m_modelFactory;\n\n  bool isIndexValid(const QModelIndex& index) const;\n};\n\ntemplate <class T>\nTabViewModel::ModelFactory getTabModelFactory() {\n  return TabViewModel::ModelFactory(\n      [](QSharedPointer<RedisClient::Connection> c, int dbIndex, QList<QByteArray> initCmd) {\n        return QSharedPointer<TabModel>(new T(c, dbIndex, initCmd),\n                                        &QObject::deleteLater);\n      });\n}\n"
  },
  {
    "path": "src/modules/connections-tree/items/abstractnamespaceitem.cpp",
    "content": "#include \"abstractnamespaceitem.h\"\n\n#include <QApplication>\n#include <QMessageBox>\n#include <QThread>\n#include <algorithm>\n#include <functional>\n\n#include \"connections-tree/keysrendering.h\"\n#include \"connections-tree/model.h\"\n#include \"connections-tree/operations.h\"\n#include \"keyitem.h\"\n#include \"loadmoreitem.h\"\n#include \"namespaceitem.h\"\n\nusing namespace ConnectionsTree;\n\nAbstractNamespaceItem::AbstractNamespaceItem(\n    Model &model, QWeakPointer<TreeItem> parent,\n    QSharedPointer<Operations> operations, uint dbIndex, QRegExp filter)\n    : TreeItem(model),\n      m_parent(parent),\n      m_operations(operations),\n      m_filter(filter.isEmpty() ? QRegExp(operations->defaultFilter())\n                                : filter),      \n      m_dbIndex(dbIndex),\n      m_runningOperation(nullptr) {\n  QSettings settings;\n  m_showNsOnTop = settings\n                      .value(\"app/showNamespacesOnTop\",\n#if defined(Q_OS_WINDOWS)\n                             true\n#else\n                             false\n#endif\n                             )\n                      .toBool();\n}\n\nQList<QSharedPointer<TreeItem>> AbstractNamespaceItem::getAllChilds() const {\n  return m_childItems;\n}\n\nQList<QSharedPointer<AbstractNamespaceItem>>\nAbstractNamespaceItem::getAllChildNamespaces() const {\n  return m_childNamespaces.values();\n}\n\nQSharedPointer<TreeItem> AbstractNamespaceItem::child(uint row) {\n  if (row < m_childItems.size()) return m_childItems.at(row);\n\n  return QSharedPointer<TreeItem>();\n}\n\nQWeakPointer<TreeItem> AbstractNamespaceItem::parent() const {\n  return m_parent;\n}\n\nvoid AbstractNamespaceItem::append(QSharedPointer<TreeItem> item, bool notifyModel) {\n  if (notifyModel)\n    m_model.beforeChildLoadedAtPos(getSelf(), m_childItems.size());\n  m_childItems.append(item);\n  if (notifyModel) m_model.childLoaded(getSelf());\n}\n\nbool compareTreeItemsByName(QSharedPointer<TreeItem> first,\n                            QSharedPointer<TreeItem> second) {\n  return first->getDisplayName() < second->getDisplayName();\n}\n\nbool compareTreeItemsByNameAndNsOnTop(QSharedPointer<TreeItem> first,\n                                      QSharedPointer<TreeItem> second) {\n  if (first->type() != second->type()) return first->type() > second->type();\n\n  return first->getDisplayName() < second->getDisplayName();\n}\n\nvoid AbstractNamespaceItem::insertChild(QSharedPointer<TreeItem> item) {\n  auto pos = std::upper_bound(m_childItems.begin(), m_childItems.end(), item,\n                              m_showNsOnTop ? compareTreeItemsByNameAndNsOnTop\n                                            : compareTreeItemsByName);\n\n  int index = std::distance(m_childItems.begin(), pos);\n\n  m_model.beforeChildLoadedAtPos(getSelf(), index);\n  m_childItems.insert(pos, item);\n  m_model.childLoaded(getSelf());\n}\n\nvoid AbstractNamespaceItem::appendKeyToIndex(QSharedPointer<KeyItem> key) {\n  if (!key) return;\n\n  m_keysIndex.insert(key->getFullPath(), key.toWeakRef());\n}\n\nvoid AbstractNamespaceItem::removeNamespacedKeysFromIndex(QByteArray nsPrefix) {\n  auto keys = m_keysIndex.keys();\n\n  for (auto const &fullPath : qAsConst(keys)) {\n    if (fullPath.startsWith(nsPrefix)) {\n      m_keysIndex.remove(fullPath);\n    }\n  }\n}\n\nQHash<QByteArray, QWeakPointer<KeyItem>> AbstractNamespaceItem::getKeysIndex()\n    const {\n  return m_keysIndex;\n}\n\nQSharedPointer<TreeItem> resolveItemToRemove(QSharedPointer<TreeItem> item) {\n  if (!item) return item;\n\n  auto parent = item->parent().toStrongRef();\n\n  if (!parent || parent->type() == \"database\") return item;\n\n  if (parent->type() == \"namespace\" && parent->getAllChilds().empty())\n    return resolveItemToRemove(parent);\n\n  return item;\n}\n\nvoid AbstractNamespaceItem::removeObsoleteKeys(\n    QList<QWeakPointer<KeyItem>> keys) {\n  for (const auto &obsoleteKey : keys) {\n    auto key = obsoleteKey.toStrongRef();\n\n    if (!key) continue;\n\n    m_keysIndex.remove(key->getFullPath());\n\n    auto parent = key->parent().toStrongRef();\n\n    if (!parent) continue;\n\n    QSharedPointer<TreeItem> itemToRemoveFromModel = key;\n\n    if (parent->type() == \"namespace\" && parent->getAllChilds().size() == 1) {\n      itemToRemoveFromModel = resolveItemToRemove(parent);\n    }\n\n    if (!itemToRemoveFromModel) continue;\n\n    int row = itemToRemoveFromModel->row();\n\n    m_model.beforeItemChildRemoved(itemToRemoveFromModel->parent(), row);\n\n    auto parentHoldsItemToRemove =\n        itemToRemoveFromModel->parent().toStrongRef();\n\n    if (!parentHoldsItemToRemove) continue;\n\n    parentHoldsItemToRemove->removeChild(row);\n\n    m_model.itemChildRemoved(itemToRemoveFromModel);\n  }\n}\n\nvoid AbstractNamespaceItem::removeChild(int index)\n{\n    bool validIndex = 0 < index && index < m_childItems.size();\n    if (!validIndex)\n        return;\n\n    m_childItems.removeAt(index);\n}\n\nvoid AbstractNamespaceItem::appendRawKey(const QByteArray &k) {\n  m_rawChildKeys.append(k);\n}\n\nvoid AbstractNamespaceItem::appendNamespace(\n    QSharedPointer<AbstractNamespaceItem> item) {\n  m_childNamespaces[item->getName()] = item;\n  insertChild(item.staticCast<TreeItem>());\n}\n\nuint AbstractNamespaceItem::childCount(bool recursive) const {\n  uint count = 0;\n\n  if (!recursive) {\n    count += m_childItems.size();\n    return count;\n  }\n\n  for (const auto &item : m_childItems) {\n    if (item->supportChildItems()) {\n      count += item->childCount(true);\n    } else {\n      count += 1;\n    }\n  }\n  return count;\n}\n\nuint AbstractNamespaceItem::keysCount() const {\n  uint count = m_rawChildKeys.size();\n\n  for (const auto &item : m_childItems) {\n    if (item->supportChildItems()) {\n      auto ns = item.dynamicCast<AbstractNamespaceItem>();\n      if (ns) {\n        count += ns->keysCount();\n      }\n    } else if (item->type() == \"key\") {\n      count += 1;\n    }\n  }\n  return count;\n}\n\nuint AbstractNamespaceItem::keysRenderingLimit() const {\n  QSettings appSettings;\n  return appSettings.value(\"app/treeItemMaxChilds\", 1000).toUInt();\n}\n\nbool AbstractNamespaceItem::keysShortNameRendering() const {\n  QSettings appSettings;\n  return appSettings.value(\"app/namespacedKeysShortName\", true).toBool();\n}\n\nvoid AbstractNamespaceItem::clear() {\n  clearLoader();\n\n  bool notifyModel = false;\n\n  if (m_childItems.size() > 0) {\n    notifyModel = true;\n    m_model.beforeItemChildsUnloaded(getSelf());\n  }\n\n  m_childItems.clear();\n  m_childNamespaces.clear();\n  m_rawChildKeys.clear();\n  m_usedMemory = 0;\n\n  if (type() == \"database\") {\n    m_keysIndex.clear();\n  } else {\n    auto selfRef = getSelf().toStrongRef().dynamicCast<AbstractNamespaceItem>();\n    if (selfRef) {\n      auto root = resolveRootItem(selfRef);\n\n      if (root) {\n        root->removeNamespacedKeysFromIndex(getFullPath());\n      }\n    }\n  }\n\n  if (notifyModel) {\n    m_model.itemChildRemoved(getSelf());\n  }\n}\n\nvoid AbstractNamespaceItem::clearLoader() {\n  if (m_childItems.empty()) {\n    return;\n  }\n\n  auto lastItem = m_childItems.last();\n\n  if (!lastItem || lastItem->type() != \"loader\") return;\n\n  m_model.beforeItemChildRemoved(getSelf(), m_childItems.size() - 1);\n  m_childItems.removeLast();\n  m_model.itemChildRemoved(lastItem.toWeakRef());\n}\n\nvoid AbstractNamespaceItem::showLoadingError(const QString &err) {\n  emit m_model.itemChanged(getSelf());\n  emit m_model.error(err);\n}\n\nvoid AbstractNamespaceItem::cancelCurrentOperation() {\n  if (m_runningOperation) {\n    m_runningOperation->future().cancel();\n    m_operations->resetConnection();\n    unlock();\n  }\n}\n\nbool compareChilds(QSharedPointer<TreeItem> first,\n                   QSharedPointer<TreeItem> second) {\n  auto firstMemoryItem = first.dynamicCast<MemoryUsage>();\n  auto secondMemoryItem = second.dynamicCast<MemoryUsage>();\n\n  if (!firstMemoryItem)\n    qDebug() << \"Invalid tree item:\" << first->getDisplayName();\n  if (!secondMemoryItem)\n    qDebug() << \"Invalid tree item:\" << second->getDisplayName();\n\n  return (firstMemoryItem ? firstMemoryItem->usedMemory() : 0) >\n         (secondMemoryItem ? secondMemoryItem->usedMemory() : 0);\n}\n\nvoid AbstractNamespaceItem::sortChilds() {\n  m_model.beforeItemLayoutChanged(getSelf());\n  std::sort(m_childItems.begin(), m_childItems.end(), compareChilds);\n  m_model.itemLayoutChanged(getSelf());\n  emit m_model.itemChanged(getSelf());\n}\n\nvoid AbstractNamespaceItem::renderRawKeys(\n    const RedisClient::Connection::RawKeysList &keylist, QRegExp filter,\n    QSharedPointer<RenderRawKeysCallback> callback, bool appendNewItems,\n    bool checkPreRenderedItems, int maxChildItems) {\n  if (!m_operations) {\n    return;\n  }\n\n  uint renderingLimit =\n      qMax(static_cast<uint>(m_childItems.size()), keysRenderingLimit());\n\n  if (maxChildItems > 0) {\n    renderingLimit = static_cast<uint>(maxChildItems);\n  } \n\n  auto settings = ConnectionsTree::KeysTreeRenderer::RenderingSettigns{\n      filter,         m_operations->getNamespaceSeparator(),\n      getDbIndex(),   renderingLimit,\n      appendNewItems, checkPreRenderedItems, keysShortNameRendering()};\n\n  auto future = QtConcurrent::run(\n      [](QList<QByteArray> keylist) {\n        std::sort(keylist.begin(), keylist.end());\n        return keylist;\n      },\n      keylist);\n\n  auto selfWPtr = getSelf();\n\n  AsyncFuture::observe(future).subscribe([selfWPtr, this, settings, callback, future]() {\n\n    auto self = selfWPtr.toStrongRef();\n\n    if (!self)\n      return;\n\n    ConnectionsTree::KeysTreeRenderer::renderKeys(\n        m_operations, future.result(),\n        qSharedPointerDynamicCast<AbstractNamespaceItem>(self), settings,\n        m_model.expandedNamespaces);\n    if (callback)\n      callback->call();\n  });\n}\n\nvoid AbstractNamespaceItem::ensureLoaderIsCreated() {\n  if (m_rawChildKeys.empty() || m_childItems.empty()) {\n    return;\n  }\n\n  auto lastItem = m_childItems.last();\n\n  if (lastItem->type() == \"loader\") return;\n\n  m_model.beforeChildLoaded(getSelf(), 1);\n  m_childItems.append(\n      QSharedPointer<TreeItem>(new LoadMoreItem(getSelf(), m_model)));\n  m_model.childLoaded(getSelf());\n}\n\nQHash<QString, std::function<bool ()> > AbstractNamespaceItem::eventHandlers() {\n  auto events = TreeItem::eventHandlers();\n\n  events.insert(\"analyze_memory_usage\", [this]() {\n    if (m_usedMemory > 0) return true;\n\n    auto future = m_operations->connectionSupportsMemoryOperations();\n\n    auto selfWPtr = getSelf();\n\n    AsyncFuture::observe(future).subscribe([selfWPtr, this](bool isSupported) {\n      auto self = selfWPtr.toStrongRef();\n\n      if (!self)\n        return;\n\n      if (!isSupported) {\n        emit m_model.error(QCoreApplication::translate(\n            \"RESP\",\n            \"Your redis-server doesn't support <a \"\n            \"href='https://redis.io/commands/memory-usage'><b>MEMORY</b></a> \"\n            \"commands.\"));\n        unlock();\n        return;\n      }\n\n      getMemoryUsage([selfWPtr, this](qlonglong) {\n        QTimer::singleShot(0, this, [this]() {\n          sortChilds();\n          unlock();\n          m_runningOperation.clear();\n        });\n      });\n    });\n    return false;\n  });\n\n  return events;\n}\n\nvoid AbstractNamespaceItem::getMemoryUsage(\n    std::function<void(qlonglong)> callback) {\n  m_usedMemory = 0;\n\n  m_runningOperation = QSharedPointer<AsyncFuture::Deferred<qlonglong>>(\n      new AsyncFuture::Deferred<qlonglong>());\n\n  QtConcurrent::run(this, &AbstractNamespaceItem::calculateUsedMemory,\n                    m_runningOperation, callback);\n\n  return;\n}\n\nvoid AbstractNamespaceItem::fetchMore() {\n  if (m_rawChildKeys.empty()) {\n    return;\n  }\n\n  clearLoader();\n\n  int childsCount = m_childItems.size();\n  auto rawKeys = m_rawChildKeys;\n\n  emit m_model.itemChanged(getSelf());\n\n  m_rawChildKeys.clear();\n\n  auto callback = QSharedPointer<RenderRawKeysCallback>(\n      new RenderRawKeysCallback(getSelf(), [this]() {\n        ensureLoaderIsCreated();\n\n        unlock();\n      }));\n\n  return renderRawKeys(\n      rawKeys, m_filter, callback,\n      true, false, static_cast<uint>(childsCount) + keysRenderingLimit());\n}\n\nvoid AbstractNamespaceItem::calculateUsedMemory(\n    QSharedPointer<AsyncFuture::Deferred<qlonglong>> parentDeffered,\n    std::function<void(qlonglong)> callback) {\n  if (parentDeffered && parentDeffered->future().isCanceled()) {\n    return;\n  }\n\n  if (m_rawChildKeys.size() > 0) {\n    auto resultCallback = QSharedPointer<Operations::GetUsedMemoryCallback>(\n        new Operations::GetUsedMemoryCallback(\n            getSelf(), [this, callback](qlonglong result) {\n              m_usedMemory = result;\n              emit m_model.itemChanged(getSelf());\n\n              if (m_childItems.empty()) callback(result);\n            }));\n\n    auto progressCallback = QSharedPointer<Operations::GetUsedMemoryCallback>(\n        new Operations::GetUsedMemoryCallback(\n            getSelf(), [this](qlonglong progress) {\n              m_usedMemory = progress;\n              emit m_model.itemChanged(getSelf());\n            }));\n\n    operations()->getUsedMemory(m_rawChildKeys, m_dbIndex, resultCallback,\n                                progressCallback);\n  }\n\n  auto resultsRemaining = QSharedPointer<qlonglong>(new qlonglong(0));\n\n  auto updateUsedMemoryValue = [this, resultsRemaining,\n                                callback](qlonglong result) {\n    if (!resultsRemaining) return;\n\n    QMutexLocker locker(&m_updateUsedMemoryMutex);\n    Q_UNUSED(locker);\n    m_usedMemory += result;\n    emit m_model.itemChanged(getSelf());\n\n    (*resultsRemaining)--;\n\n    if (*resultsRemaining <= 0) {\n      callback(m_usedMemory);\n    }\n  };\n\n  (*resultsRemaining) += m_childNamespaces.size();\n\n  for (auto childNs : qAsConst(m_childNamespaces)) {\n    if (parentDeffered->future().isCanceled()) {\n      return;\n    }\n    childNs->calculateUsedMemory(parentDeffered, updateUsedMemoryValue);\n  }\n\n  for (const QSharedPointer<TreeItem> &child : qAsConst(m_childItems)) {\n    if (parentDeffered->future().isCanceled()) {\n      return;\n    }\n\n    if (!child || child->type() != \"key\") continue;\n\n    auto memoryItem = child.dynamicCast<MemoryUsage>();\n\n    if (!memoryItem) continue;\n\n    QMutexLocker locker(&m_updateUsedMemoryMutex);\n    (*resultsRemaining)++;\n    memoryItem->getMemoryUsage(updateUsedMemoryValue);\n  }\n}\n\nvoid AbstractNamespaceItem::restoreOpenedNamespaces(QSharedPointer<AbstractNamespaceItem> ns) {\n  if (ns->type() == \"namespace\" && !ns->isExpanded()) return;\n\n  if (ns->isExpanded())\n      m_model.expandItem(ns.staticCast<TreeItem>().toWeakRef());\n\n  auto childs = ns->getAllChildNamespaces();\n\n  for (auto childNs : childs) {\n    restoreOpenedNamespaces(childNs);\n  }\n}\n"
  },
  {
    "path": "src/modules/connections-tree/items/abstractnamespaceitem.h",
    "content": "#pragma once\n#include <qredisclient/connection.h>\n#include <QRegExp>\n#include <QSharedPointer>\n#include <QString>\n#include <QtConcurrent>\n\n#include \"memoryusage.h\"\n#include \"treeitem.h\"\n#include \"modules/common/callbackwithowner.h\"\n\nnamespace ConnectionsTree {\n\nclass Operations;\nclass AbstractNamespaceItem;\nclass Model;\nclass KeyItem;\n\nclass AbstractNamespaceItem : public QObject, public TreeItem, public MemoryUsage {\n\n public:\n  AbstractNamespaceItem(Model& model, QWeakPointer<TreeItem> parent,\n                        QSharedPointer<Operations> operations, uint dbIndex,\n                        QRegExp filter = QRegExp());\n\n  virtual ~AbstractNamespaceItem() {}\n\n  QList<QSharedPointer<TreeItem>> getAllChilds() const override;\n\n  QList<QSharedPointer<AbstractNamespaceItem>> getAllChildNamespaces() const;\n\n  uint childCount(bool recursive = false) const override;\n\n  uint keysCount() const;\n\n  uint keysRenderingLimit() const;\n\n  bool keysShortNameRendering() const;\n\n  QSharedPointer<TreeItem> child(uint row) override;\n\n  QWeakPointer<TreeItem> parent() const override;\n\n  virtual void append(QSharedPointer<TreeItem> item, bool notifyModel=true);\n\n  virtual void insertChild(QSharedPointer<TreeItem> item);\n\n  virtual void appendKeyToIndex(QSharedPointer<KeyItem> key);\n\n  virtual void removeNamespacedKeysFromIndex(QByteArray nsPrefix);\n\n  virtual QHash<QByteArray, QWeakPointer<KeyItem>> getKeysIndex() const;\n\n  virtual void removeObsoleteKeys(QList<QWeakPointer<KeyItem>> keys);\n\n  void removeChild(int index) override;\n\n  virtual void appendRawKey(const QByteArray& k);\n\n  virtual void appendNamespace(QSharedPointer<AbstractNamespaceItem> item);\n\n  virtual QSharedPointer<AbstractNamespaceItem> findChildNamespace(\n      const QByteArray& name) {\n    if (!m_childNamespaces.contains(name))\n      return QSharedPointer<AbstractNamespaceItem>();\n\n    return m_childNamespaces[name];\n  }\n\n  virtual uint getDbIndex() { return m_dbIndex; }\n\n  virtual QSharedPointer<Operations> operations() { return m_operations; }\n\n  virtual QRegExp getFilter() const { return m_filter; }\n\n  virtual void showLoadingError(const QString& err);\n\n  void cancelCurrentOperation() override;\n\n  void getMemoryUsage(std::function<void(qlonglong)>) override; \n\n  void fetchMore() override;\n\n protected:\n  virtual void clear();\n\n  virtual void ensureLoaderIsCreated();\n\n  virtual void clearLoader();\n\n  void sortChilds();\n\n  using RenderRawKeysCallback = CallbackWithOwner<TreeItem>;\n\n  void renderRawKeys(const RedisClient::Connection::RawKeysList& keylist, QRegExp filter,\n                     QSharedPointer<RenderRawKeysCallback> callback,\n                     bool appendNewItems,\n                     bool checkPreRenderedItems,\n                     int maxChildItems=-1);  \n\n  QHash<QString, std::function<bool()>> eventHandlers() override;\n\n  void calculateUsedMemory(QSharedPointer<AsyncFuture::Deferred<qlonglong>> parentD, std::function<void(qlonglong)> callback);\n\n  void restoreOpenedNamespaces(QSharedPointer<AbstractNamespaceItem> ns);\n\n protected:\n  QWeakPointer<TreeItem> m_parent;\n  QSharedPointer<Operations> m_operations;\n  QList<QSharedPointer<TreeItem>> m_childItems;\n  QHash<QByteArray, QSharedPointer<AbstractNamespaceItem>> m_childNamespaces;\n  QList<QByteArray> m_rawChildKeys;\n  QRegExp m_filter;  \n  uint m_dbIndex;\n  QSharedPointer<AsyncFuture::Deferred<qlonglong>> m_runningOperation;\n  bool m_showNsOnTop;  \n  QHash<QByteArray, QWeakPointer<KeyItem>> m_keysIndex;  \n};\n}  // namespace ConnectionsTree\n"
  },
  {
    "path": "src/modules/connections-tree/items/databaseitem.cpp",
    "content": "#include \"databaseitem.h\"\r\n\r\n#include <QDebug>\r\n#include <QFutureWatcher>\r\n#include <QInputDialog>\r\n#include <QMenu>\r\n#include <QMessageBox>\r\n#include <algorithm>\r\n#include <functional>\r\n#include <typeinfo>\r\n\r\n#include \"app/apputils.h\"\r\n#include \"connections-tree/model.h\"\r\n#include \"connections-tree/utils.h\"\r\n#include \"keyitem.h\"\r\n#include \"loadmoreitem.h\"\r\n#include \"namespaceitem.h\"\r\n#include \"serveritem.h\"\r\n\r\nusing namespace ConnectionsTree;\r\n\r\nDatabaseItem::DatabaseItem(unsigned int index, int keysCount,\r\n                           QSharedPointer<Operations> operations,\r\n                           QWeakPointer<TreeItem> parent, Model& model)\r\n    : AbstractNamespaceItem(model, parent, operations, index),\r\n      m_keysCount(keysCount) {}\r\n\r\nDatabaseItem::~DatabaseItem() {}\r\n\r\nQByteArray DatabaseItem::getName() const { return QByteArray(); }\r\n\r\nQByteArray DatabaseItem::getFullPath() const { return QByteArray(); }\r\n\r\nQString DatabaseItem::getDisplayName() const {\r\n  QString filter = m_filter.pattern() == \"*\"\r\n                       ? \"\"\r\n                       : QString(\"[filter: %1]\").arg(m_filter.pattern());\r\n\r\n  QString baseString = QString(\"db%1\").arg(m_dbIndex);\r\n\r\n  if (m_usedMemory > 0) {\r\n    baseString.append(\r\n        QString(\" <b>[%1]</b>\").arg(humanReadableSize(m_usedMemory)));\r\n  }\r\n\r\n  if (m_operations->mode() == \"cluster\") {\r\n    return QString(\"%1 %2\").arg(baseString).arg(filter);\r\n  } else {\r\n    return QString(\"%1 %2 (%3)\").arg(baseString).arg(filter).arg(m_keysCount);\r\n  }\r\n}\r\n\r\nbool DatabaseItem::isEnabled() const { return true; }\r\n\r\nvoid DatabaseItem::loadKeys(std::function<void()> callback,\r\n                            bool partialReload) {\r\n  lock();\r\n\r\n  QString filter = (m_filter.isEmpty()) ? \"\" : m_filter.pattern();\r\n\r\n  auto self = getSelf().toStrongRef();\r\n\r\n  if (!self) {\r\n    unlock();\r\n    return;\r\n  }\r\n\r\n  auto dbLoadCallback = QSharedPointer<Operations::GetDatabasesCallback>(\r\n      new Operations::GetDatabasesCallback(\r\n          getSelf(), [this](QMap<int, int> dbMapping, const QString& err) {\r\n            if (err.size() > 0) {\r\n              unlock();\r\n              emit m_model.error(QCoreApplication::translate(\r\n                                     \"RESP\", \"Cannot load databases:\\n\\n\") +\r\n                                 err);\r\n              return;\r\n            }\r\n\r\n            if (dbMapping.contains(m_dbIndex)) {\r\n              m_keysCount = dbMapping[m_dbIndex];\r\n              emit m_model.itemChanged(getSelf());\r\n            }\r\n          }));\r\n\r\n  m_operations->getDatabases(dbLoadCallback);\r\n\r\n  auto onKeysRendered = QSharedPointer<RenderRawKeysCallback>(\r\n      new RenderRawKeysCallback(getSelf(), [this, callback]() {\r\n        ensureLoaderIsCreated();\r\n        unlock();\r\n\r\n        if (!isExpanded()) {\r\n          setExpanded(true);\r\n          m_model.expandItem(getSelf());\r\n        }\r\n\r\n        emit m_model.itemChanged(getSelf());\r\n\r\n        if (callback) {\r\n          callback();\r\n        }\r\n      }));\r\n\r\n  auto nsItemsCallback = QSharedPointer<Operations::LoadNamespaceItemsCallback>(\r\n      new Operations::LoadNamespaceItemsCallback(\r\n          getSelf(), [this, onKeysRendered, partialReload](\r\n                         const RedisClient::Connection::RawKeysList& keylist,\r\n                         const QString& err) {\r\n            if (!err.isEmpty()) {\r\n              unlock();\r\n              return showLoadingError(err);\r\n            }\r\n\r\n            return renderRawKeys(keylist, m_filter, onKeysRendered,\r\n                                 !partialReload, partialReload);\r\n          }));\r\n\r\n  m_operations->loadNamespaceItems(m_dbIndex, filter, nsItemsCallback);\r\n}\r\n\r\nQVariantMap DatabaseItem::metadata() const {\r\n  QVariantMap metadata = TreeItem::metadata();\r\n  metadata[\"filter\"] = m_filter.pattern();\r\n  metadata[\"filterHistory\"] = filterHistoryTop10();\r\n  metadata[\"live_update\"] = isLiveUpdateEnabled();\r\n  metadata[\"user_color\"] = m_operations->iconColor();\r\n  return metadata;\r\n}\r\n\r\nvoid DatabaseItem::setMetadata(const QString& key, QVariant value) {\r\n  bool isResetValue = (value.isNull() || !value.canConvert<QString>() ||\r\n                       value.toString().isEmpty());\r\n\r\n  if (key == \"filter\") {\r\n    if (!m_filter.isEmpty() && isResetValue)\r\n      return resetFilter();\r\n    else if (isResetValue)\r\n      return;\r\n\r\n    auto applyFilter = [this, value]() {\r\n      QRegExp pattern(value.toString(), Qt::CaseSensitive,\r\n                      QRegExp::PatternSyntax::WildcardUnix);\r\n      filterKeys(pattern);\r\n    };\r\n\r\n    QByteArray val = value.toByteArray();\r\n\r\n    if (val.contains('*')) {\r\n      return applyFilter();\r\n    }\r\n\r\n    auto selfWPtr = getSelf();\r\n\r\n    auto openKeyCallback = QSharedPointer<Operations::OpenKeyIfExistsCallback>(\r\n        new Operations::OpenKeyIfExistsCallback(\r\n            selfWPtr, [applyFilter](const QString&, bool result) {\r\n              if (!result) {\r\n                applyFilter();\r\n              }\r\n            }));\r\n\r\n    auto self = selfWPtr.toStrongRef();\r\n\r\n    if (!self) return;\r\n\r\n    m_operations->openKeyIfExists(val, self.dynamicCast<DatabaseItem>(),\r\n                                  openKeyCallback);\r\n\r\n    return;\r\n  } else if (key == \"live_update\") {\r\n    if (liveUpdateTimer()->isActive() && isResetValue) {\r\n      qDebug() << \"Stop live update\";\r\n      liveUpdateTimer()->stop();\r\n    } else {\r\n      qDebug() << \"Start live update\";\r\n      liveUpdateTimer()->start();\r\n    }\r\n\r\n    emit m_model.itemChanged(getSelf());\r\n  }\r\n}\r\n\r\nvoid DatabaseItem::getMemoryUsage(std::function<void(qlonglong)> callback) {\r\n  if (m_childItems.size() == 0) {\r\n    auto d = QSharedPointer<AsyncFuture::Deferred<qlonglong>>(\r\n        new AsyncFuture::Deferred<qlonglong>());\r\n    loadKeys([this, callback]() {\r\n      lock();\r\n      AbstractNamespaceItem::getMemoryUsage(callback);\r\n    });\r\n  } else {\r\n    AbstractNamespaceItem::getMemoryUsage(callback);\r\n  }\r\n}\r\n\r\nvoid DatabaseItem::unload(bool notify) {\r\n  if (m_childItems.size() == 0) return;\r\n\r\n  lock();\r\n  clear();\r\n\r\n  m_keysCount = 0;\r\n\r\n  if (notify) m_operations->notifyDbWasUnloaded(m_dbIndex);\r\n\r\n  unlock();\r\n}\r\n\r\nvoid DatabaseItem::reload(std::function<void()> callback) {\r\n  clear();\r\n  loadKeys([this, callback]() {\r\n    QSettings settings;\r\n    m_model.expandedNamespaces.clear();\r\n\r\n    if (settings.value(\"app/reopenNamespacesOnReload\", true).toBool()) {\r\n      auto self = getSelf().toStrongRef();\r\n\r\n      if (!self) return;\r\n\r\n      restoreOpenedNamespaces(self.staticCast<AbstractNamespaceItem>());\r\n    }\r\n\r\n    if (callback) callback();\r\n  });\r\n}\r\n\r\nvoid DatabaseItem::performLiveUpdate() {\r\n  qDebug() << \"Live update loading keys...\";\r\n\r\n  if (isLocked()) {\r\n    qDebug()\r\n        << \"Another loading operation is in progress. Skip this live update...\";\r\n    liveUpdateTimer()->start();\r\n    return;\r\n  }\r\n\r\n  m_rawChildKeys.clear();\r\n\r\n  loadKeys(\r\n      [this]() {\r\n        QSettings settings;\r\n        if (m_childItems.size() >=\r\n            settings.value(\"app/liveUpdateKeysLimit\", 1000).toInt()) {\r\n          liveUpdateTimer()->stop();\r\n\r\n          emit m_model.itemChanged(getSelf());\r\n\r\n          QMessageBox::warning(\r\n              nullptr,\r\n              QCoreApplication::translate(\"RESP\", \"Live update was disabled\"),\r\n              QCoreApplication::translate(\r\n                  \"RESP\",\r\n                  \"Live update was disabled due to exceeded keys limit. \"\r\n                  \"Please specify filter more carefully or change limit in \"\r\n                  \"settings.\"));\r\n        } else {\r\n          liveUpdateTimer()->start();\r\n          emit m_model.itemChanged(getSelf());\r\n        }\r\n      },\r\n      true);\r\n}\r\n\r\nvoid DatabaseItem::filterKeys(const QRegExp& filter) {\r\n  m_filter = filter;\r\n  emit m_model.itemChanged(getSelf());\r\n  reload();\r\n}\r\n\r\nvoid DatabaseItem::resetFilter() {\r\n  m_filter = QRegExp(m_operations->defaultFilter());\r\n  emit m_model.itemChanged(getSelf());\r\n  reload();\r\n}\r\n\r\nQHash<QString, std::function<bool ()> > DatabaseItem::eventHandlers() {\r\n  auto events = AbstractNamespaceItem::eventHandlers();\r\n\r\n  events.insert(\"click\", [this]() {\r\n    if (m_childItems.size() != 0) {\r\n      if (!isExpanded()) {\r\n        setExpanded(true);\r\n        m_model.expandItem(getSelf());\r\n      }\r\n      return true;\r\n    }\r\n\r\n    loadKeys();\r\n    return false;\r\n  });\r\n\r\n  events.insert(\"right-click\", [this]() {\r\n    if (m_childItems.size() != 0) return true;\r\n\r\n    emit m_model.itemChanged(getSelf());\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"add_key\", [this]() {\r\n    auto callback = QSharedPointer<Operations::OpenNewKeyDialogCallback>(\r\n        new Operations::OpenNewKeyDialogCallback(getSelf(), [this]() {\r\n          confirmAction(\r\n              nullptr,\r\n              QCoreApplication::translate(\r\n                  \"RESP\",\r\n                  \"Key was added. Do you want to reload keys in \"\r\n                  \"selected database?\"),\r\n              [this]() {\r\n                reload();\r\n                m_keysCount++;\r\n              },\r\n              QCoreApplication::translate(\"RESP\", \"Key was added\"));\r\n        }));\r\n\r\n    m_operations->openNewKeyDialog(m_dbIndex, callback);\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"reload\", [this]() {\r\n    reload();\r\n    return false;\r\n  });\r\n\r\n  events.insert(\"flush\", [this]() {\r\n    confirmAction(\r\n        nullptr,\r\n        QCoreApplication::translate(\r\n            \"RESP\",\r\n            \"Do you really want to remove all keys from this database?\"),\r\n        [this]() {\r\n          auto callback = QSharedPointer<Operations::FlushDbCallback>(\r\n              new Operations::FlushDbCallback(\r\n                  getSelf(), [this](const QString&) { unload(); }));\r\n          m_operations->flushDb(m_dbIndex, callback);\r\n        });\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"console\",\r\n                [this]() { m_operations->openConsoleTab(m_dbIndex); return true; });\r\n\r\n  events.insert(\"delete_keys\", [this]() { m_operations->deleteDbKeys(*this); return true; });\r\n\r\n  events.insert(\"copy_keys\", [this]() { m_operations->copyKeys(*this); return true; });\r\n\r\n  events.insert(\"rdb_import\",\r\n                [this]() { m_operations->importKeysFromRdb(*this); return true; });\r\n\r\n  events.insert(\"ttl\", [this]() { m_operations->setTTL(*this); return true; });\r\n\r\n  return events;\r\n}\r\n\r\nQSharedPointer<QTimer> DatabaseItem::liveUpdateTimer() {\r\n  if (!m_liveUpdateTimer) {\r\n    QSettings settings;\r\n    m_liveUpdateTimer = QSharedPointer<QTimer>(new QTimer());\r\n    m_liveUpdateTimer->setInterval(\r\n        settings.value(\"app/liveUpdateInterval\", 10).toInt() * 1000);\r\n\r\n    qDebug() << \"Live update timer\"\r\n             << settings.value(\"app/liveUpdateInterval\", 10).toInt() * 1000;\r\n\r\n    m_liveUpdateTimer->setSingleShot(true);\r\n\r\n    QObject::connect(m_liveUpdateTimer.data(), &QTimer::timeout,\r\n                     [this]() { performLiveUpdate(); });\r\n  }\r\n\r\n  return m_liveUpdateTimer;\r\n}\r\n\r\nbool DatabaseItem::isLiveUpdateEnabled() const {\r\n  return m_liveUpdateTimer && m_liveUpdateTimer->isActive();\r\n}\r\n\r\n// Top 10 filters\r\nQVariantList DatabaseItem::filterHistoryTop10() const {\r\n  typedef QPair<QString, int> FilterUsage;\r\n\r\n  QList<FilterUsage> filterHistoryRating;\r\n  QVariantList filterHistoryList;\r\n  auto server = parent().toStrongRef();\r\n\r\n  if (!server || !server.staticCast<ServerItem>()) return filterHistoryList;\r\n\r\n  QVariantMap filterHistory = m_operations->getFilterHistory();\r\n  QVariantMap::const_iterator i(filterHistory.begin());\r\n\r\n  while (i != filterHistory.end()) {\r\n    FilterUsage filterUsage;\r\n    filterUsage.first = i.key();\r\n    filterUsage.second = i.value().toInt();\r\n    filterHistoryRating.append(filterUsage);\r\n    ++i;\r\n  }\r\n  std::sort(filterHistoryRating.begin(), filterHistoryRating.end(),\r\n            [](FilterUsage i, FilterUsage j) { return (i.second > j.second); });\r\n\r\n  for (int i = 0; filterHistoryRating.size() > 0; i++) {\r\n    if (i >= 10) break;\r\n    filterHistoryList.append(filterHistoryRating.takeFirst().first);\r\n  }\r\n  return filterHistoryList;\r\n}\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/databaseitem.h",
    "content": "#pragma once\r\n#include \"abstractnamespaceitem.h\"\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass ServerItem;\r\n\r\nclass DatabaseItem : public AbstractNamespaceItem {\r\n public:\r\n  DatabaseItem(unsigned int index, int keysCount,\r\n               QSharedPointer<Operations> operations,\r\n               QWeakPointer<TreeItem> parent, Model& model);\r\n\r\n  ~DatabaseItem();\r\n\r\n  QByteArray getName() const override;\r\n\r\n  QByteArray getFullPath() const override;\r\n\r\n  QString getDisplayName() const override;\r\n\r\n  QString type() const override { return \"database\"; }\r\n\r\n  bool isEnabled() const override;\r\n\r\n  QVariantMap metadata() const override;\r\n\r\n  void setMetadata(const QString&, QVariant) override;\r\n\r\n  void getMemoryUsage(std::function<void(qlonglong)> callback) override;\r\n\r\n  void reload(std::function<void()> callback = std::function<void()>());\r\n\r\n protected:\r\n  void loadKeys(std::function<void()> callback = std::function<void()>(),\r\n                bool partialReload=false);\r\n  void unload(bool notify = true);\r\n  void performLiveUpdate();\r\n  void filterKeys(const QRegExp& filter);\r\n  void resetFilter();\r\n\r\n  QHash<QString, std::function<bool()>> eventHandlers() override;\r\n\r\n private:\r\n  QSharedPointer<QTimer> liveUpdateTimer();\r\n  bool isLiveUpdateEnabled() const;\r\n  QVariantList filterHistoryTop10() const;\r\n\r\n private:\r\n  unsigned int m_keysCount;\r\n  QSharedPointer<QTimer> m_liveUpdateTimer;\r\n};\r\n\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/keyitem.cpp",
    "content": "#include \"keyitem.h\"\r\n#include <qredisclient/utils/text.h>\r\n#include <QCoreApplication>\r\n#include <QMenu>\r\n#include <QMessageBox>\r\n#include <QSettings>\r\n\r\n#include \"app/apputils.h\"\r\n#include \"connections-tree/items/abstractnamespaceitem.h\"\r\n#include \"connections-tree/model.h\"\r\n#include \"connections-tree/utils.h\"\r\n\r\nusing namespace ConnectionsTree;\r\n\r\nQSharedPointer<AbstractNamespaceItem> parentTreeItemToNs(\r\n    QWeakPointer<TreeItem> p) {\r\n  auto parentNs = p.toStrongRef();\r\n\r\n  if (!parentNs || !parentNs.staticCast<AbstractNamespaceItem>())\r\n    return QSharedPointer<AbstractNamespaceItem>();\r\n\r\n  return parentNs.staticCast<AbstractNamespaceItem>();\r\n}\r\n\r\nKeyItem::KeyItem(const QByteArray& fullPath, QWeakPointer<TreeItem> parent,\r\n                 Model& model, bool shortNameRendering)\r\n    : TreeItem(model),\r\n      m_fullPath(fullPath),\r\n      m_parent(parent),\r\n      m_removed(false),\r\n      m_shortRendering(shortNameRendering)\r\n{\r\n}\r\n\r\nQString KeyItem::getDisplayName() const {\r\n  QString title;\r\n\r\n  if (m_parent && m_parent.toStrongRef()->type() == \"namespace\" &&\r\n      m_shortRendering) {\r\n    auto parent = parentTreeItemToNs(m_parent);\r\n\r\n    title = printableString(getFullPath().mid(\r\n        parent->getFullPath().size() +\r\n        parent->operations()->getNamespaceSeparator().size()));\r\n  } else {\r\n    title = printableString(getFullPath(), true);\r\n  }\r\n\r\n  if (m_usedMemory > 0) {\r\n    title.append(QString(\" <b>[%1]</b>\").arg(humanReadableSize(m_usedMemory)));\r\n  }\r\n\r\n  return title;\r\n}\r\n\r\nQByteArray KeyItem::getName() const { return getFullPath(); }\r\n\r\nQList<QSharedPointer<TreeItem>> KeyItem::getAllChilds() const {\r\n  return QList<QSharedPointer<TreeItem>>();\r\n}\r\n\r\nbool KeyItem::supportChildItems() const { return false; }\r\n\r\nuint KeyItem::childCount(bool) const { return 0u; }\r\n\r\nQSharedPointer<TreeItem> KeyItem::child(uint) {\r\n  return QSharedPointer<TreeItem>();\r\n}\r\n\r\nQWeakPointer<TreeItem> KeyItem::parent() const { return m_parent; }\r\n\r\nbool KeyItem::isEnabled() const {\r\n  if (!m_removed && m_parent) {\r\n    return m_parent.toStrongRef()->isEnabled();\r\n  } else {\r\n    return m_removed == false;\r\n  }\r\n}\r\n\r\nQByteArray KeyItem::getFullPath() const { return m_fullPath; }\r\n\r\nint KeyItem::getDbIndex() const {\r\n  auto parentNs = parentTreeItemToNs(m_parent);\r\n\r\n  if (!parentNs) {\r\n    return -1;\r\n  }\r\n\r\n  return parentNs->getDbIndex();\r\n}\r\n\r\nvoid KeyItem::setRemoved() {\r\n  m_removed = true;\r\n\r\n  emit m_model.itemChanged(getSelf());\r\n}\r\n\r\nvoid KeyItem::getMemoryUsage(std::function<void(qlonglong)> callback) {\r\n  auto parentNs = parentTreeItemToNs(m_parent);\r\n\r\n  if (!parentNs || !parentNs->operations()) return callback(0);\r\n\r\n  auto cb = QSharedPointer<Operations::GetUsedMemoryCallback>(\r\n      new Operations::GetUsedMemoryCallback(\r\n          getSelf(), [this, callback](qlonglong result) {\r\n            m_usedMemory = result;\r\n            callback(result);\r\n            emit m_model.itemChanged(getSelf());\r\n          }));\r\n\r\n  parentNs->operations()->getUsedMemory(\r\n      {getFullPath()}, getDbIndex(), cb,\r\n      QSharedPointer<Operations::GetUsedMemoryCallback>());\r\n}\r\n\r\nvoid KeyItem::setFullPath(const QByteArray& p) {\r\n  m_fullPath = p;\r\n\r\n  emit m_model.itemChanged(getSelf());\r\n}\r\n\r\nQHash<QString, std::function<bool()>> KeyItem::eventHandlers() {\r\n  auto events = TreeItem::eventHandlers();\r\n\r\n  events.insert(\"click\", [this]() {\r\n    if (!isEnabled()) return true;\r\n\r\n    auto parentNs = parentTreeItemToNs(m_parent);\r\n\r\n    if (!parentNs || !parentNs->operations()) return true;\r\n\r\n    parentNs->operations()->openKeyTab(\r\n        getSelf().toStrongRef().staticCast<KeyItem>(), false);\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"mid-click\", [this]() {\r\n    if (!isEnabled()) return true;\r\n\r\n    auto parentNs = parentTreeItemToNs(m_parent);\r\n\r\n    if (!parentNs || !parentNs->operations()) return true;\r\n\r\n    parentNs->operations()->openKeyTab(\r\n        getSelf().toStrongRef().staticCast<KeyItem>(), true);\r\n\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"delete\", [this]() {\r\n    confirmAction(\r\n        nullptr,\r\n        QCoreApplication::translate(\"RESP\",\r\n                                    \"Do you really want to delete this key?\"),\r\n        [this]() {\r\n          auto parentNs = parentTreeItemToNs(m_parent);\r\n\r\n          if (!parentNs || !parentNs->operations()) return;\r\n\r\n          auto callback = QSharedPointer<Operations::DeleteDbKeyCallback>(\r\n              new Operations::DeleteDbKeyCallback(\r\n                  getSelf(), [this](const QString& err) {\r\n                    emit m_model.error(QCoreApplication::translate(\r\n                                           \"RESP\", \"Cannot delete key:\\n\\n\") +\r\n                                       err);\r\n                    return;\r\n                  }));\r\n\r\n          parentNs->operations()->deleteDbKey(*this, callback);\r\n        });\r\n    return true;\r\n  });\r\n  return events;\r\n}\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/keyitem.h",
    "content": "#pragma once\r\n\r\n#include \"connections-tree/operations.h\"\r\n#include \"memoryusage.h\"\r\n#include \"treeitem.h\"\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass KeyItem : public TreeItem, public MemoryUsage {\r\n public:\r\n  KeyItem(const QByteArray& fullPath, QWeakPointer<TreeItem> parent,\r\n          Model& model, bool shortNameRendering);\r\n\r\n  QString getDisplayName() const override;\r\n\r\n  QByteArray getName() const override;\r\n\r\n  QString type() const override { return \"key\"; }\r\n\r\n  QList<QSharedPointer<TreeItem>> getAllChilds() const override;\r\n\r\n  bool supportChildItems() const override;\r\n\r\n  uint childCount(bool recursive = false) const override;\r\n\r\n  QSharedPointer<TreeItem> child(uint) override;\r\n\r\n  QWeakPointer<TreeItem> parent() const override;\r\n\r\n  bool isEnabled() const override;\r\n\r\n  QByteArray getFullPath() const override;\r\n\r\n  int getDbIndex() const;\r\n\r\n  void setRemoved();\r\n\r\n  void setFullPath(const QByteArray& p);\r\n\r\n  void getMemoryUsage(std::function<void(qlonglong)> callback) override;\r\n\r\n protected:\r\n  QHash<QString, std::function<bool()>> eventHandlers() override;\r\n\r\n private:\r\n  QByteArray m_fullPath;\r\n  QWeakPointer<TreeItem> m_parent;\r\n  bool m_removed;\r\n  bool m_shortRendering;\r\n};\r\n\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/loadmoreitem.cpp",
    "content": "#include \"loadmoreitem.h\"\n\nConnectionsTree::LoadMoreItem::LoadMoreItem(QWeakPointer<TreeItem> parent, Model &model)\n    : ConnectionsTree::TreeItem::TreeItem(model),\n      m_parent(parent)\n{\n\n}\n\nQString ConnectionsTree::LoadMoreItem::getDisplayName() const\n{\n    return QCoreApplication::translate(\"RESP\", \"Load more keys\");\n}\n\nQList<QSharedPointer<ConnectionsTree::TreeItem> > ConnectionsTree::LoadMoreItem::getAllChilds() const\n{\n    return {};\n}\n\nbool ConnectionsTree::LoadMoreItem::supportChildItems() const\n{\n    return false;\n}\n\nuint ConnectionsTree::LoadMoreItem::childCount(bool) const\n{\n    return 0u;\n}\n\nQSharedPointer<ConnectionsTree::TreeItem> ConnectionsTree::LoadMoreItem::child(uint)\n{\n    return QSharedPointer<ConnectionsTree::TreeItem>();\n}\n\nQWeakPointer<ConnectionsTree::TreeItem> ConnectionsTree::LoadMoreItem::parent() const\n{\n    return m_parent;\n}\n\nbool ConnectionsTree::LoadMoreItem::isEnabled() const\n{\n    return true;\n}\n\nQHash<QString, std::function<bool()> > ConnectionsTree::LoadMoreItem::eventHandlers()\n{\n    QHash<QString, std::function<bool()>> events;\n    events[\"click\"] = [this]() {\n        auto parentPtr = m_parent.toStrongRef();\n        if (!parentPtr) {\n            return true;\n        }        \n        parentPtr->fetchMore();\n        return false;\n    };\n    return events;\n}\n"
  },
  {
    "path": "src/modules/connections-tree/items/loadmoreitem.h",
    "content": "#pragma once\n\n#include \"connections-tree/operations.h\"\n#include \"treeitem.h\"\n\nnamespace ConnectionsTree {\n\nclass LoadMoreItem : public TreeItem {\n public:\n  LoadMoreItem(QWeakPointer<TreeItem> parent, Model& model);\n\n  QString getDisplayName() const override;\n\n  QString type() const override { return \"loader\"; }\n\n  QList<QSharedPointer<TreeItem>> getAllChilds() const override;\n\n  bool supportChildItems() const override;\n\n  uint childCount(bool recursive = false) const override;\n\n  QSharedPointer<TreeItem> child(uint) override;\n\n  QWeakPointer<TreeItem> parent() const override;\n\n  bool isEnabled() const override;\n\n protected:\n  QHash<QString, std::function<bool()>> eventHandlers() override;\n\n private:\n  QWeakPointer<TreeItem> m_parent;\n};\n\n}  // namespace ConnectionsTree\n"
  },
  {
    "path": "src/modules/connections-tree/items/memoryusage.h",
    "content": "#pragma once\n#include <QFuture>\n#include <QSharedPointer>\n#include <QString>\n#include <functional>\n\n#include \"treeitem.h\"\n\nnamespace ConnectionsTree {\nclass MemoryUsage {\n public:\n  MemoryUsage() : m_usedMemory(0) {}\n  virtual ~MemoryUsage() {}\n\n  virtual void getMemoryUsage(std::function<void(qlonglong)> callback) = 0;\n\n  qlonglong usedMemory() const { return m_usedMemory; }\n\n protected:\n  qlonglong m_usedMemory;\n  QMutex m_updateUsedMemoryMutex;\n};\n}  // namespace ConnectionsTree\n"
  },
  {
    "path": "src/modules/connections-tree/items/namespaceitem.cpp",
    "content": "#include \"namespaceitem.h\"\r\n#include <qredisclient/utils/text.h>\r\n#include <QMenu>\r\n#include <QMessageBox>\r\n#include \"app/apputils.h\"\r\n#include \"connections-tree/model.h\"\r\n#include \"connections-tree/utils.h\"\r\n#include \"connections-tree/keysrendering.h\"\r\n#include \"databaseitem.h\"\r\n#include \"keyitem.h\"\r\n#include \"loadmoreitem.h\"\r\n\r\nusing namespace ConnectionsTree;\r\n\r\nNamespaceItem::NamespaceItem(const QByteArray &fullPath,\r\n                             QSharedPointer<Operations> operations,\r\n                             QWeakPointer<TreeItem> parent, Model &model,\r\n                             uint dbIndex, QRegExp filter)\r\n    : AbstractNamespaceItem(model, parent, operations, dbIndex, filter),\r\n      m_fullPath(fullPath),\r\n      m_removed(false) {}\r\n\r\nQString NamespaceItem::getDisplayName() const {\r\n  QString title = QString(\"%1 (%2)\")\r\n                      .arg(printableString(getName(), true))\r\n                      .arg(keysCount());\r\n\r\n  if (m_usedMemory > 0) {\r\n    title.append(QString(\" <b>[%1]</b>\").arg(humanReadableSize(m_usedMemory)));\r\n  }\r\n\r\n  return title;\r\n}\r\n\r\nQByteArray NamespaceItem::getName() const {\r\n  qsizetype pos = m_fullPath.lastIndexOf(m_operations->getNamespaceSeparator());\r\n\r\n  if (pos >= 0) {\r\n      return m_fullPath.mid(pos + m_operations->getNamespaceSeparator().size());\r\n  } else {\r\n      return m_fullPath;\r\n  }\r\n}\r\n\r\nbool NamespaceItem::isEnabled() const { return m_removed == false; }\r\n\r\nQByteArray NamespaceItem::getFullPath() const { return m_fullPath; }\r\n\r\nvoid NamespaceItem::setRemoved() {\r\n  m_removed = true;\r\n\r\n  clear();\r\n\r\n  emit m_model.itemChanged(getSelf());\r\n}\r\n\r\nQVariantMap NamespaceItem::metadata() const {\r\n  QVariantMap metadata = TreeItem::metadata();\r\n  metadata[\"full_path\"] = getFullPath();\r\n  return metadata;\r\n}\r\n\r\nvoid NamespaceItem::load() {\r\n  auto onKeysRendered = QSharedPointer<RenderRawKeysCallback>(\r\n      new RenderRawKeysCallback(getSelf(), [this]() {\r\n        ensureLoaderIsCreated();\r\n\r\n        unlock();\r\n        setExpanded(true);\r\n        emit m_model.itemChanged(getSelf());\r\n        m_model.expandItem(getSelf());\r\n      }));\r\n\r\n  if (m_rawChildKeys.size() > 0) {\r\n    auto rawKeys = m_rawChildKeys;\r\n    m_rawChildKeys.clear();\r\n\r\n    return renderRawKeys(rawKeys, m_filter, onKeysRendered, true, false);\r\n  }\r\n\r\n  QString nsFilter = QString(\"%1%2*\")\r\n                         .arg(QString::fromUtf8(m_fullPath))\r\n                         .arg(m_operations->getNamespaceSeparator());\r\n\r\n  if (!m_filter.isEmpty()) {\r\n    if (m_filter.pattern().startsWith(nsFilter.chopped(1))) {\r\n      nsFilter = m_filter.pattern();\r\n    } else {\r\n      nsFilter = QString(\"%1%2%3\")\r\n                     .arg(QString::fromUtf8(m_fullPath))\r\n                     .arg(m_operations->getNamespaceSeparator())\r\n                     .arg(m_filter.pattern());\r\n    }\r\n  }\r\n\r\n  auto callback = QSharedPointer<Operations::LoadNamespaceItemsCallback>(\r\n      new Operations::LoadNamespaceItemsCallback(\r\n          getSelf(), [this, nsFilter, onKeysRendered](\r\n                         const RedisClient::Connection::RawKeysList &keylist,\r\n                         const QString &err) {\r\n            if (!err.isEmpty()) {\r\n              unlock();\r\n              return showLoadingError(err);\r\n            }\r\n\r\n            return renderRawKeys(keylist, m_filter, onKeysRendered, true,\r\n                                 false);\r\n          }));\r\n\r\n  m_operations->loadNamespaceItems(m_dbIndex, nsFilter, callback);\r\n}\r\n\r\nvoid NamespaceItem::reload() {\r\n  clear();\r\n  load();\r\n}\r\n\r\nQHash<QString, std::function<bool()>> NamespaceItem::eventHandlers() {\r\n  auto events = AbstractNamespaceItem::eventHandlers();\r\n\r\n  events.insert(\"click\", [this]() {\r\n    if (m_childItems.size() == 0) {\r\n      load();\r\n      return false;\r\n    } else if (!isExpanded()) {\r\n      setExpanded(true);\r\n      emit m_model.itemChanged(getSelf());\r\n      m_model.expandItem(getSelf());      \r\n    }\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"add_key\", [this]() {\r\n    auto callback = QSharedPointer<Operations::OpenNewKeyDialogCallback>(\r\n        new Operations::OpenNewKeyDialogCallback(getSelf(), [this]() {\r\n          confirmAction(\r\n              nullptr,\r\n              QCoreApplication::translate(\r\n                  \"RESP\",\r\n                  \"Key was added. Do you want to reload keys in \"\r\n                  \"selected namespace?\"),\r\n              [this]() { reload(); },\r\n              QCoreApplication::translate(\"RESP\", \"Key was added\"));\r\n        }));\r\n    m_operations->openNewKeyDialog(\r\n        m_dbIndex, callback,\r\n        QString(\"%1%2\")\r\n            .arg(QString::fromUtf8(getFullPath()))\r\n            .arg(m_operations->getNamespaceSeparator()));\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"reload\", [this]() { reload(); return false; });\r\n\r\n  events.insert(\"delete\", [this]() { m_operations->deleteDbNamespace(*this); return true; });\r\n\r\n  return events;\r\n}\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/namespaceitem.h",
    "content": "#pragma once\r\n#include \"abstractnamespaceitem.h\"\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass NamespaceItem : public AbstractNamespaceItem {\r\n  Q_OBJECT\r\n\r\n public:\r\n  NamespaceItem(const QByteArray& fullPath,\r\n                QSharedPointer<Operations> operations,\r\n                QWeakPointer<TreeItem> parent, Model& model, uint dbIndex);\r\n\r\n public:\r\n  NamespaceItem(const QByteArray& fullPath,\r\n                QSharedPointer<Operations> operations,\r\n                QWeakPointer<TreeItem> parent, Model& model, uint dbIndex,\r\n                QRegExp filter);\r\n\r\n  QString getDisplayName() const override;\r\n\r\n  QByteArray getName() const override;\r\n\r\n  QByteArray getFullPath() const override;\r\n\r\n  QString type() const override { return \"namespace\"; }\r\n\r\n  bool isEnabled() const override;\r\n\r\n  void setRemoved();\r\n\r\n  QVariantMap metadata() const override;\r\n\r\n protected:\r\n  void load();\r\n\r\n  void reload();\r\n\r\n  QHash<QString, std::function<bool()>> eventHandlers() override;\r\n\r\n private:\r\n  QByteArray m_fullPath;\r\n  bool m_removed;\r\n};\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/servergroup.cpp",
    "content": "#include \"servergroup.h\"\r\n\r\n#include \"connections-tree/model.h\"\r\n#include \"connections-tree/items/serveritem.h\"\r\n#include \"connections-tree/utils.h\"\r\n\r\nusing namespace ConnectionsTree;\r\n\r\nServerGroup::ServerGroup(const QString& name, Model& model)\r\n    : SortableTreeItem(model), m_name(name) {}\r\n\r\nQString ServerGroup::getDisplayName() const { return m_name; }\r\n\r\nQList<QSharedPointer<TreeItem> > ServerGroup::getAllChilds() const {\r\n  return m_servers;\r\n}\r\n\r\nuint ServerGroup::childCount(bool) const {\r\n  return static_cast<uint>(m_servers.size());\r\n}\r\n\r\nQSharedPointer<TreeItem> ServerGroup::child(uint row) {\r\n  if (row < m_servers.size()) {\r\n    return m_servers.at(row);\r\n  }\r\n\r\n  return QSharedPointer<TreeItem>();\r\n}\r\n\r\nQSharedPointer<TreeItem> ServerGroup::takeChild(uint row)\r\n{\r\n   if (!child(row))\r\n       return QSharedPointer<TreeItem>();\r\n\r\n   return m_servers.takeAt(row);\r\n}\r\n\r\nvoid ServerGroup::insertChildAt(uint row, QSharedPointer<TreeItem> srv)\r\n{\r\n    if (!srv)\r\n        return;\r\n\r\n    m_servers.insert(row, srv);\r\n}\r\n\r\nvoid ServerGroup::removeConnection(QSharedPointer<TreeItem> srv)\r\n{\r\n    m_servers.removeAll(srv);\r\n}\r\n\r\nQHash<QString, std::function<bool ()> > ServerGroup::eventHandlers() {\r\n  auto events = TreeItem::eventHandlers();\r\n\r\n  events.insert(\"edit\", [this]() { emit editActionRequested(); return true; });\r\n\r\n  events.insert(\"delete\", [this]() {\r\n    confirmAction(nullptr,\r\n                  QCoreApplication::translate(\r\n                      \"RESP\", \"Do you really want to delete group <b>with all connections</b>?\"),\r\n                  [this]() { emit deleteActionRequested(); });\r\n    return true;\r\n  });\r\n\r\n  return events;\r\n}\r\n\r\nvoid ServerGroup::setName(const QString& name) { m_name = name; }\r\n\r\nvoid ServerGroup::addServer(QSharedPointer<TreeItem> s) {\r\n    auto srv = s.dynamicCast<ServerItem>();\r\n    m_servers.append(s);\r\n}\r\n\r\nbool ServerGroup::isExpanded() const { return true; }\r\n\r\nvoid ServerGroup::unload() {\r\n  for (auto child : m_servers) {\r\n    auto item = child.dynamicCast<SortableTreeItem>();\r\n\r\n    if (!item) continue;\r\n\r\n    item->unload();\r\n  }\r\n}\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/servergroup.h",
    "content": "#pragma once\r\n#include <QList>\r\n#include <QObject>\r\n#include \"connections-tree/operations.h\"\r\n#include \"sortabletreeitem.h\"\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass Model;\r\n\r\nclass ServerGroup : public QObject, public SortableTreeItem {\r\n    Q_OBJECT\r\n public:\r\n  ServerGroup(const QString &name, Model& m);\r\n\r\n  QString getDisplayName() const override;\r\n\r\n  QString type() const override { return \"server_group\"; }\r\n\r\n  QList<QSharedPointer<TreeItem>> getAllChilds() const override;\r\n\r\n  uint childCount(bool recursive = false) const override;\r\n\r\n  QSharedPointer<TreeItem> child(uint row) override;\r\n\r\n  QSharedPointer<TreeItem> takeChild(uint row);\r\n\r\n  void insertChildAt(uint row, QSharedPointer<TreeItem> srv);\r\n\r\n  void removeConnection(QSharedPointer<TreeItem> srv);\r\n\r\n  void setName(const QString &name);\r\n\r\n  void addServer(QSharedPointer<TreeItem> s);\r\n\r\n  bool isExpanded() const override;\r\n\r\n  void unload() override;\r\n\r\n signals:\r\n  void editActionRequested();\r\n  void deleteActionRequested();\r\n\r\n protected:\r\n  QHash<QString, std::function<bool()>> eventHandlers() override;\r\n\r\n private:\r\n  QString m_name;\r\n\r\n  QList<QSharedPointer<TreeItem>> m_servers;\r\n};\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/serveritem.cpp",
    "content": "#include \"serveritem.h\"\r\n\r\n#include <asyncfuture.h>\r\n\r\n#include <QAction>\r\n#include <QDebug>\r\n#include <QMenu>\r\n#include <QMessageBox>\r\n#include <algorithm>\r\n#include <functional>\r\n\r\n#include \"connections-tree/model.h\"\r\n#include \"connections-tree/utils.h\"\r\n#include \"databaseitem.h\"\r\n\r\nusing namespace ConnectionsTree;\r\n\r\nServerItem::ServerItem(QSharedPointer<Operations> operations, Model& model,\r\n                       QWeakPointer<TreeItem> parent)\r\n    : SortableTreeItem(model),\r\n      m_operations(operations),\r\n      m_parent(parent) {}\r\n\r\nServerItem::~ServerItem() {}\r\n\r\nQString ServerItem::getDisplayName() const { return m_operations->connectionName(); }\r\n\r\nQList<QSharedPointer<TreeItem> > ServerItem::getAllChilds() const {\r\n  return m_databases;\r\n}\r\n\r\nuint ServerItem::childCount(bool) const {\r\n  return static_cast<uint>(m_databases.size());\r\n}\r\n\r\nQSharedPointer<TreeItem> ServerItem::child(uint row) {\r\n  if (row < m_databases.size()) {\r\n    return m_databases.at(row);\r\n  }\r\n\r\n  return QSharedPointer<TreeItem>();\r\n}\r\n\r\nQWeakPointer<TreeItem> ServerItem::parent() const {\r\n    return m_parent;\r\n}\r\n\r\nvoid ServerItem::setParent(QWeakPointer<TreeItem> p)\r\n{\r\n    m_parent = p;\r\n}\r\n\r\nbool ServerItem::isDatabaseListLoaded() const {\r\n  return m_databases.size() > 0;\r\n}\r\n\r\nQSharedPointer<Operations> ServerItem::getOperations() { return m_operations; }\r\n\r\nint ServerItem::row() const\r\n{\r\n    if (!parent()) {\r\n        return m_row;\r\n    }\r\n\r\n    return TreeItem::row();\r\n}\r\n\r\nvoid ServerItem::load() {\r\n  auto callback = QSharedPointer<Operations::GetDatabasesCallback>(\r\n      new Operations::GetDatabasesCallback(\r\n          getSelf(),\r\n          [this](RedisClient::DatabaseList databases, const QString& err) {\r\n            if (err.size() > 0) {\r\n              unlock();\r\n              emit m_model.error(QCoreApplication::translate(\r\n                                     \"RESP\", \"Cannot load databases:\\n\\n\") +\r\n                                 err);\r\n              return;\r\n            }\r\n\r\n            if (databases.size() == 0) {\r\n              unlock();\r\n              return;\r\n            }\r\n\r\n            RedisClient::DatabaseList::const_iterator db =\r\n                databases.constBegin();\r\n            QList<QSharedPointer<TreeItem>> dbs;\r\n            while (db != databases.constEnd()) {\r\n              QSharedPointer<TreeItem> database((new DatabaseItem(\r\n                  db.key(), db.value(), m_operations, m_self, m_model)));\r\n\r\n              dbs.push_back(database);\r\n              ++db;\r\n            }\r\n\r\n            QTimer::singleShot(0, this, [this, dbs]() {\r\n              m_model.beforeChildLoaded(getSelf(), dbs.size());\r\n              m_databases = dbs;\r\n              m_model.childLoaded(getSelf());\r\n\r\n              unlock();\r\n              m_model.expandItem(getSelf());\r\n            });\r\n          }));\r\n\r\n  m_currentOperation = m_operations->getDatabases(callback);\r\n\r\n  if (!m_currentOperation.isRunning()) {\r\n    unlock();\r\n  }\r\n}\r\n\r\nvoid ServerItem::unload() {\r\n  if (!isDatabaseListLoaded()) return;\r\n\r\n  m_model.beforeItemChildsUnloaded(m_self);\r\n\r\n  m_operations->disconnect();\r\n\r\n  for (auto db : m_databases) {\r\n      auto dbItem = db.staticCast<DatabaseItem>();\r\n\r\n      if (dbItem && m_operations) {\r\n          m_operations->notifyDbWasUnloaded(dbItem->getDbIndex());\r\n      }\r\n  }\r\n\r\n  m_databases.clear();\r\n  m_model.itemChildRemoved(getSelf());  \r\n}\r\n\r\nvoid ServerItem::reload() {\r\n  unload();\r\n  load();\r\n}\r\n\r\nvoid ServerItem::edit() {\r\n  unload();\r\n  emit editActionRequested();\r\n}\r\n\r\nvoid ServerItem::remove() {\r\n  unload();\r\n  emit deleteActionRequested();\r\n}\r\n\r\nvoid ServerItem::openConsole() { m_operations->openConsoleTab(); }\r\n\r\nQHash<QString, std::function<bool()> > ServerItem::eventHandlers() {\r\n  auto events = TreeItem::eventHandlers();\r\n\r\n  events.insert(\"click\", [this]() {\r\n    m_operations->openServerStats();\r\n\r\n    if (isDatabaseListLoaded()) {\r\n        if (!isExpanded()) {\r\n            setExpanded(true);\r\n            m_model.expandItem(getSelf());\r\n        }\r\n        return true;\r\n    }\r\n\r\n    load();    \r\n    return false;\r\n  });\r\n\r\n  events.insert(\"console\", [this]() { m_operations->openConsoleTab(); return true; });\r\n\r\n  auto openServerContextTab = [this]() { m_operations->openServerStats(); return true; };\r\n  events.insert(\"right-click\", openServerContextTab);\r\n  events.insert(\"mid-click\", openServerContextTab);\r\n\r\n  events.insert(\"duplicate\", [this]() { m_operations->duplicateConnection(); return true; });\r\n\r\n  events.insert(\"reload\", [this]() {\r\n    reload();\r\n    return false;\r\n  });\r\n\r\n  events.insert(\"unload\", [this]() { unload(); return true; });\r\n\r\n  events.insert(\"edit\", [this]() {\r\n    auto unloadAction = [this]() {\r\n      unload();\r\n      emit editActionRequested();\r\n    };\r\n\r\n    if (m_operations->isConnected()) {\r\n      confirmAction(nullptr,\r\n                    QCoreApplication::translate(\r\n                        \"RESP\",\r\n                        \"Value and Console tabs related to this \"\r\n                        \"connection will be closed. Do you want to continue?\"),\r\n                    unloadAction);\r\n    } else {\r\n      unloadAction();\r\n    }\r\n    return true;\r\n  });\r\n\r\n  events.insert(\"delete\", [this]() {\r\n    confirmAction(nullptr,\r\n                  QCoreApplication::translate(\r\n                      \"RESP\", \"Do you really want to delete connection?\"),\r\n                  [this]() {\r\n                    unload();\r\n                    emit deleteActionRequested();\r\n                  });\r\n    return true;\r\n  });\r\n\r\n  return events;\r\n}\r\n\r\nvoid ServerItem::setWeakPointer(QWeakPointer<ServerItem> self) {\r\n  m_self = self;\r\n  m_selfPtr = self;\r\n}\r\n\r\nQVariantMap ConnectionsTree::ServerItem::metadata() const {\r\n  QVariantMap meta = TreeItem::metadata();\r\n\r\n  if (isDatabaseListLoaded()) {\r\n    meta[\"server_type\"] = m_operations->mode();\r\n  } else {\r\n    meta[\"server_type\"] = \"unknown\";\r\n  }\r\n\r\n  meta[\"user_color\"] = m_operations->iconColor();\r\n\r\n  return meta;\r\n}\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/serveritem.h",
    "content": "#pragma once\r\n#include <QList>\r\n#include <QObject>\r\n\r\n#include \"connections-tree/operations.h\"\r\n#include \"sortabletreeitem.h\"\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass Model;\r\n\r\nclass ServerItem : public QObject, public SortableTreeItem {\r\n  Q_OBJECT\r\n public:\r\n  ServerItem(QSharedPointer<Operations> operations,\r\n             Model &model,\r\n             QWeakPointer<TreeItem> parent = QWeakPointer<TreeItem>());\r\n\r\n  ~ServerItem();\r\n\r\n  QString getDisplayName() const override;\r\n\r\n  QString type() const override { return \"server\"; }\r\n\r\n  QVariantMap metadata() const override;\r\n\r\n  QList<QSharedPointer<TreeItem>> getAllChilds() const override;\r\n\r\n  uint childCount(bool recursive = false) const override;\r\n\r\n  QSharedPointer<TreeItem> child(uint row) override;\r\n\r\n  QWeakPointer<TreeItem> parent() const override;\r\n\r\n  void setParent(QWeakPointer<TreeItem> p);\r\n\r\n  void setWeakPointer(QWeakPointer<ServerItem>);\r\n\r\n  bool isDatabaseListLoaded() const;\r\n\r\n  QSharedPointer<Operations> getOperations();\r\n\r\n  int row() const override;\r\n\r\n public slots:\r\n  void unload() override;\r\n\r\n private slots:\r\n  void load();\r\n  void reload();\r\n  void edit();\r\n  void remove();\r\n  void openConsole();\r\n\r\n signals:\r\n  void editActionRequested();\r\n  void deleteActionRequested();\r\n\r\n protected:\r\n  QHash<QString, std::function<bool()>> eventHandlers() override;\r\n\r\n private:\r\n  QSharedPointer<Operations> m_operations;\r\n  QList<QSharedPointer<TreeItem>> m_databases;\r\n  QWeakPointer<ServerItem> m_self;\r\n  QWeakPointer<TreeItem> m_parent;\r\n  QModelIndex m_index;\r\n};\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/items/sortabletreeitem.h",
    "content": "#pragma once\n\n#include \"treeitem.h\"\n\nnamespace ConnectionsTree {\n\nclass Model;\n\nclass SortableTreeItem : public TreeItem {\n\npublic:\n  SortableTreeItem(Model& model)\n    : TreeItem(model), m_row(0) {}\n\n  int row() const override { return m_row; }\n\n  void setRow(int r) { m_row = r; }\n\n  virtual void unload() = 0;\n\nprotected:\n  int m_row;\n};\n\n}\n"
  },
  {
    "path": "src/modules/connections-tree/items/treeitem.cpp",
    "content": "#include \"treeitem.h\"\n#include \"connections-tree/model.h\"\n\nConnectionsTree::TreeItem::TreeItem(Model &m)\n    : m_model(m), m_locked(false), m_expanded(false) {}\n\nQVariantMap ConnectionsTree::TreeItem::metadata() const {\n  QVariantMap meta;\n  meta[\"name\"] = getDisplayName();\n  meta[\"full_name\"] = getName();\n  meta[\"type\"] = type();\n  meta[\"locked\"] = isLocked();\n  meta[\"state\"] = isEnabled();\n  return meta;\n}\n\nint ConnectionsTree::TreeItem::row() const {\n  if (!parent()) return 0;\n\n  auto p = parent().toStrongRef();\n\n  for (uint index = 0; index < p->childCount(); ++index) {\n    if (p->child(index).data() == this) return index;\n  }\n\n  return 0;\n}\n\nQWeakPointer<ConnectionsTree::TreeItem> ConnectionsTree::TreeItem::getSelf() {\n  if (m_selfPtr) return m_selfPtr;\n\n  if (!parent()) return QWeakPointer<TreeItem>();\n\n  QSharedPointer<TreeItem> p = parent().toStrongRef();\n\n  if (!p) return QWeakPointer<TreeItem>();\n\n  m_selfPtr = p->child(row()).toWeakRef();\n\n  return m_selfPtr;\n}\n\nConnectionsTree::Model &ConnectionsTree::TreeItem::model() { return m_model; }\n\nvoid ConnectionsTree::TreeItem::lock() {\n  m_locked = true;\n  if (getSelf())\n    emit m_model.itemChanged(getSelf());\n}\n\nvoid ConnectionsTree::TreeItem::unlock() {\n  m_locked = false;\n  if (getSelf())\n    emit m_model.itemChanged(getSelf());\n}\n\nQHash<QString, std::function<bool()>>\nConnectionsTree::TreeItem::eventHandlers() {\n  QHash<QString, std::function<bool()>> events;\n  events[\"cancel\"] = [this]() { cancelCurrentOperation(); return true; };\n  return events;\n}\n\nvoid ConnectionsTree::TreeItem::handleEvent(QString event) {\n  if (!eventHandlers().contains(event)) return;\n\n  if (isLocked() && event != \"cancel\") {\n    qDebug() << \"Item is locked. Ignore event: \" << event;\n\n    emit m_model.itemChanged(getSelf());\n    return;\n  }\n\n  auto handler = eventHandlers()[event];\n\n  try {\n    lock();\n    bool shouldUnlock = handler();\n\n    if (shouldUnlock) {\n      unlock();\n    }\n  } catch (...) {\n    qWarning() << \"Error on event processing: \" << event;\n    unlock();\n  }\n}\n\nvoid ConnectionsTree::TreeItem::cancelCurrentOperation() {\n  m_currentOperation.cancel();\n}\n"
  },
  {
    "path": "src/modules/connections-tree/items/treeitem.h",
    "content": "#pragma once\r\n#include <QDebug>\r\n#include <QFuture>\r\n#include <QHash>\r\n#include <QIcon>\r\n#include <QList>\r\n#include <QModelIndex>\r\n#include <QSharedPointer>\r\n#include <QString>\r\n#include <QVariantMap>\r\n#include <QWeakPointer>\r\n#include <functional>\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass Model;\r\n\r\nclass TreeItem {\r\n public:\r\n  TreeItem(Model& m);\r\n\r\n  virtual ~TreeItem() {}\r\n\r\n  virtual QString getDisplayName() const = 0;\r\n\r\n  virtual QByteArray getName() const { return getDisplayName().toUtf8(); }\r\n\r\n  virtual QByteArray getFullPath() const { return QByteArray(); }\r\n\r\n  virtual QString type() const = 0;\r\n\r\n  virtual QList<QSharedPointer<TreeItem>> getAllChilds() const = 0;\r\n\r\n  virtual uint childCount(bool recursive = false) const = 0;\r\n\r\n  virtual QSharedPointer<TreeItem> child(uint row) = 0;\r\n\r\n  virtual void removeChild(int) {};\r\n\r\n  virtual QWeakPointer<TreeItem> parent() const { return QWeakPointer<TreeItem>(); }\r\n\r\n  virtual bool supportChildItems() const { return true; }  \r\n\r\n  virtual QVariantMap metadata() const;\r\n\r\n  virtual void setMetadata(const QString&, QVariant) {}\r\n\r\n  virtual int row() const;\r\n\r\n  virtual QWeakPointer<TreeItem> getSelf();\r\n\r\n  virtual void handleEvent(QString event);\r\n\r\n  virtual void cancelCurrentOperation();\r\n\r\n  virtual bool isLocked() const { return m_locked; }\r\n\r\n  virtual bool isEnabled() const { return true; };\r\n\r\n  virtual bool isExpanded() const { return m_expanded; }\r\n\r\n  virtual void setExpanded(bool v) { m_expanded = v; }\r\n\r\n  virtual void fetchMore() {}\r\n\r\n  virtual Model& model();\r\n\r\n protected:\r\n  void lock();\r\n  void unlock();\r\n  virtual QHash<QString, std::function<bool ()> > eventHandlers();\r\n\r\n protected:\r\n  Model& m_model;  \r\n  QWeakPointer<TreeItem> m_selfPtr;\r\n  bool m_locked;\r\n  bool m_expanded;\r\n  QFuture<void> m_currentOperation;\r\n};\r\n\r\ntypedef QList<QSharedPointer<TreeItem>> TreeItems;\r\n\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/keysrendering.cpp",
    "content": "#include \"keysrendering.h\"\n\n#include <QtGlobal>\n\n#include \"items/abstractnamespaceitem.h\"\n#include \"items/keyitem.h\"\n#include \"items/namespaceitem.h\"\n#include \"model.h\"\n\nusing namespace ConnectionsTree;\n\nQSharedPointer<AbstractNamespaceItem> ConnectionsTree::resolveRootItem(QSharedPointer<AbstractNamespaceItem> item) {\n  if (!item) return QSharedPointer<AbstractNamespaceItem>();\n\n  if (item->type() == \"database\") {\n      return item;\n  }\n\n  auto parent = item->parent().toStrongRef();\n\n  if (!parent) return QSharedPointer<AbstractNamespaceItem>();\n\n  if (parent->type() == \"database\")\n    return parent.dynamicCast<AbstractNamespaceItem>();\n\n  return resolveRootItem(parent.dynamicCast<AbstractNamespaceItem>());\n}\n\nvoid KeysTreeRenderer::renderKeys(QSharedPointer<Operations> operations,\n                                  RedisClient::Connection::RawKeysList keys,\n                                  QSharedPointer<AbstractNamespaceItem> parent,\n                                  RenderingSettigns settings,\n                                  const QSet<QByteArray> &expandedNamespaces) {\n  // init\n  QElapsedTimer timer;\n  timer.start();\n\n  int unprocessedPartStart = 0;\n  if (parent->getFullPath().size() > 0 || parent->type() == \"namespace\") {\n    unprocessedPartStart =\n        parent->getFullPath().size() + settings.nsSeparator.length();\n  }\n\n  auto rootItem = resolveRootItem(parent);\n\n  QHash<QByteArray, QWeakPointer<KeyItem>> preRenderedKeys;\n\n  if (rootItem) {\n      qDebug() << \"Root item resolved\";\n      preRenderedKeys = rootItem->getKeysIndex();\n  }\n  qDebug() << \"Pre-rendered keys: \" << preRenderedKeys.size();\n\n  auto preRenderedKeysList = preRenderedKeys.keys();\n  QSet<QByteArray> preRenderedKeysSet = QSet<QByteArray>(preRenderedKeysList.begin(),\n                                                         preRenderedKeysList.end());\n  QSet<QByteArray> preRenderedKeysToBeRemoved;\n\n  if (settings.checkPreRenderedItems) {\n    preRenderedKeysToBeRemoved = QSet<QByteArray>(preRenderedKeysSet.begin(),\n                                                  preRenderedKeysSet.end());\n  }\n\n  qDebug() << \"Live update: \" << settings.checkPreRenderedItems;\n\n  QByteArray rawKey;\n  QByteArray nextKey;\n\n  QList<QByteArray> bulkInsertItems;\n\n  auto isBulkInsert = [settings, preRenderedKeysSet, unprocessedPartStart](\n                          const QByteArray &current, const QByteArray &next) {\n    return (settings.appendNewItems &&\n            current.indexOf(settings.nsSeparator, unprocessedPartStart) == -1 &&\n            !next.isEmpty() &&\n            next.indexOf(settings.nsSeparator, unprocessedPartStart) == -1 &&\n            !preRenderedKeysSet.contains(next));\n  };\n\n  while (!keys.isEmpty()) {\n    rawKey = keys.takeFirst();\n\n    if (preRenderedKeysSet.contains(rawKey)) {\n        if (preRenderedKeysToBeRemoved.contains(rawKey)) {\n            preRenderedKeysToBeRemoved.remove(rawKey);\n        }\n        continue;\n    }\n\n    if (keys.size() > 0) {\n        nextKey = keys[0];\n    } else {\n        nextKey = QByteArray();\n    }\n\n    if (isBulkInsert(rawKey, nextKey)) {\n        bulkInsertItems.append(rawKey);\n        continue;\n    } else if (bulkInsertItems.size() > 0 && parent) {\n      int itemsAboutToBeInserted =\n          qMin(static_cast<uint>(bulkInsertItems.size()),\n               settings.renderLimit - parent->getAllChilds().size());\n\n      qDebug() << \"Bulk insert\" << itemsAboutToBeInserted;\n\n      if (itemsAboutToBeInserted > 0)\n          parent->model().beforeChildLoaded(parent.toWeakRef(),\n                                            itemsAboutToBeInserted);\n\n      for (const auto &item : bulkInsertItems) {\n        if (parent->getAllChilds().size() >= settings.renderLimit) {\n          parent->appendRawKey(item);\n        } else {\n          QSharedPointer<KeyItem> newKey(new KeyItem(\n              item, parent, parent->model(), settings.shortKeysRendering));\n\n          parent->append(newKey, false);\n\n          if (rootItem && rootItem->type() == \"database\") {\n            rootItem->appendKeyToIndex(newKey);\n          }\n        }\n      }\n      if (itemsAboutToBeInserted > 0)\n        parent->model().childLoaded(parent.toWeakRef());\n\n      bulkInsertItems.clear();\n    }\n\n    try {\n      renderLazily(rootItem, parent, rawKey.mid(unprocessedPartStart), rawKey, operations,\n                   settings, expandedNamespaces, 0, nextKey);\n    } catch (std::bad_alloc &) {\n      parent->showLoadingError(\"Not enough memory to render all keys\");\n      break;\n    }\n  }  \n\n  if (preRenderedKeysToBeRemoved.size() > 0) {\n    QList<QWeakPointer<KeyItem>> obsoleteKeys;\n\n    for (const auto &keyFullPath : qAsConst(preRenderedKeysToBeRemoved)) {\n      obsoleteKeys.append(preRenderedKeys[keyFullPath]);\n    }\n\n    parent->removeObsoleteKeys(obsoleteKeys);\n  }\n\n  qDebug() << \"Tree builded in: \" << timer.elapsed() << \" ms\";\n}\n\nvoid KeysTreeRenderer::renderLazily(QSharedPointer<AbstractNamespaceItem> root,\n    QSharedPointer<AbstractNamespaceItem> parent,\n    const QByteArray &notProcessedKeyPart, const QByteArray &fullKey,\n    QSharedPointer<Operations> m_operations, const RenderingSettigns &settings,\n    const QSet<QByteArray> &expandedNamespaces, unsigned long level,\n    const QByteArray &nextKey) {\n  Q_ASSERT(parent);\n\n  if (level > 0 && parent->isExpanded() == false) {\n    parent->appendRawKey(fullKey);\n    return;\n  }\n\n  QWeakPointer<TreeItem> currentParent =\n      parent.staticCast<TreeItem>().toWeakRef();\n\n  int indexOfNaspaceSeparator =\n      (settings.nsSeparator.isEmpty())\n          ? -1\n          : notProcessedKeyPart.indexOf(settings.nsSeparator);\n\n  if (indexOfNaspaceSeparator == -1) {\n    if (parent->getAllChilds().size() >= settings.renderLimit) {\n      parent->appendRawKey(fullKey);\n    } else {\n      QSharedPointer<KeyItem> newKey(new KeyItem(fullKey, currentParent,\n                                                 parent->model(),\n                                                 settings.shortKeysRendering));\n\n      if (settings.appendNewItems) {\n        parent->append(newKey);\n      } else {\n        parent->insertChild(newKey);\n      }\n\n      if (root && root->type() == \"database\") {\n        root->appendKeyToIndex(newKey);\n      }\n    }\n    return;\n  }\n\n  QByteArray firstNamespaceName =\n      notProcessedKeyPart.mid(0, indexOfNaspaceSeparator);\n\n  QSharedPointer<AbstractNamespaceItem> namespaceItem =\n      parent->findChildNamespace(firstNamespaceName);\n\n  if (namespaceItem.isNull()) {\n    long nsPos =\n        fullKey.size() - notProcessedKeyPart.size() + firstNamespaceName.size();\n    QByteArray namespaceFullPath = fullKey.mid(0, nsPos);\n\n    // Single namespaced key\n    if (nextKey.isEmpty() || nextKey.indexOf(namespaceFullPath) == -1) {\n      QSharedPointer<KeyItem> newKey(new KeyItem(fullKey, currentParent,\n                                                 parent->model(),\n                                                 settings.shortKeysRendering));\n      parent->append(newKey);\n\n      if (root && root->type() == \"database\") {\n        root->appendKeyToIndex(newKey);\n      }\n      return;\n    }\n\n    namespaceItem = QSharedPointer<NamespaceItem>(\n        new NamespaceItem(namespaceFullPath, m_operations, currentParent,\n                          parent->model(), settings.dbIndex, settings.filter));\n\n    if (expandedNamespaces.contains(namespaceFullPath)) {\n      namespaceItem->setExpanded(true);\n    }\n\n    parent->appendNamespace(namespaceItem);\n  }\n\n  renderLazily(root, namespaceItem,\n               notProcessedKeyPart.mid(indexOfNaspaceSeparator +\n                                       settings.nsSeparator.length()),\n               fullKey, m_operations, settings, expandedNamespaces,\n               level + 1, nextKey);\n}\n"
  },
  {
    "path": "src/modules/connections-tree/keysrendering.h",
    "content": "#pragma once\n#include <QRegExp>\n#include <QString>\n#include <QSharedPointer>\n#include <QtConcurrent>\n#include <qredisclient/connection.h>\n\nnamespace ConnectionsTree {\n\n    class Operations;\n    class AbstractNamespaceItem;\n    class Model;\n\n    QSharedPointer<AbstractNamespaceItem> resolveRootItem(QSharedPointer<AbstractNamespaceItem> item);\n\n    class KeysTreeRenderer\n    {\n    public:\n        struct RenderingSettigns {\n            QRegExp filter;\n            QString nsSeparator;\n            uint dbIndex;            \n            uint renderLimit;            \n            bool appendNewItems;\n            bool checkPreRenderedItems;\n            bool shortKeysRendering;\n        };\n\n    public:\n        static void renderKeys(QSharedPointer<Operations> operations,\n                               RedisClient::Connection::RawKeysList keys,\n                               QSharedPointer<AbstractNamespaceItem> parent,\n                               RenderingSettigns settings,\n                               const QSet<QByteArray> &expandedNamespaces);                \n\n    private:\n        static void renderLazily(QSharedPointer<AbstractNamespaceItem> root,\n                                 QSharedPointer<AbstractNamespaceItem> parent,\n                                 const QByteArray &notProcessedKeyPart,\n                                 const QByteArray &fullKey,\n                                 QSharedPointer<Operations> operations,\n                                 const RenderingSettigns& settings,\n                                 const QSet<QByteArray> &expandedNamespaces,\n                                 unsigned long level=0,\n                                 const QByteArray &nextKey=QByteArray());\n    };\n\n}\n"
  },
  {
    "path": "src/modules/connections-tree/model.cpp",
    "content": "#include \"model.h\"\r\n#include <QDebug>\r\n#include <QSettings>\r\n#include <QWeakPointer>\r\n#include <algorithm>\r\n#include \"items/serveritem.h\"\r\n#include \"items/servergroup.h\"\r\n#include \"items/databaseitem.h\"\r\n\r\nusing namespace ConnectionsTree;\r\n\r\nModel::Model(QObject *parent)\r\n    : QAbstractItemModel(parent),\r\n      m_rawPointers(new QHash<TreeItem *, QWeakPointer<TreeItem>>())\r\n{\r\n  qRegisterMetaType<QWeakPointer<TreeItem>>(\"QWeakPointer<TreeItem>\");\r\n  QObject::connect(this, &Model::itemChanged, this, &Model::onItemChanged);\r\n}\r\n\r\nQVariant Model::data(const QModelIndex &index, int role) const {\r\n  const TreeItem *item = getItemFromIndex(index);\r\n\r\n  if (item == nullptr) return QVariant();\r\n\r\n  if (role == itemMetaData) return item->metadata();\r\n\r\n  return QVariant();\r\n}\r\n\r\nQHash<int, QByteArray> Model::roleNames() const {\r\n  QHash<int, QByteArray> roles;\r\n  roles[itemMetaData] = \"metadata\";\r\n  return roles;\r\n}\r\n\r\nQt::ItemFlags Model::flags(const QModelIndex &index) const {\r\n  const TreeItem *item = getItemFromIndex(index);\r\n\r\n  if (item == nullptr) return Qt::NoItemFlags;\r\n\r\n  Qt::ItemFlags result = Qt::ItemIsSelectable;\r\n\r\n  if (item->isEnabled()) result |= Qt::ItemIsEnabled;\r\n\r\n  return result;\r\n}\r\n\r\nQModelIndex Model::index(int row, int column, const QModelIndex &parent) const {\r\n  if (!hasIndex(row, column, parent)) return QModelIndex();\r\n\r\n  TreeItem *parentItem = getItemFromIndex(parent);\r\n  QSharedPointer<TreeItem> childItem;\r\n\r\n  // get item from root items\r\n  if (parentItem) {\r\n    childItem = parentItem->child(row);\r\n  } else if (row < m_treeItems.size()) {\r\n    childItem = m_treeItems.at(row);\r\n  }\r\n\r\n  if (!childItem) return QModelIndex();\r\n\r\n  m_rawPointers->insert(childItem.data(), childItem.toWeakRef());\r\n  return createIndex(row, column, childItem.data());\r\n}\r\n\r\nQModelIndex Model::parent(const QModelIndex &index) const {\r\n  const TreeItem *childItem = getItemFromIndex(index);\r\n\r\n  if (!childItem) return QModelIndex();\r\n\r\n  QWeakPointer<TreeItem> parentItem = childItem->parent();\r\n\r\n  if (!parentItem) return QModelIndex();\r\n\r\n  auto parentStrongRef = parentItem.toStrongRef();\r\n\r\n  if (!parentStrongRef) return QModelIndex();\r\n\r\n  m_rawPointers->insert(parentStrongRef.data(), parentItem);\r\n  return createIndex(parentStrongRef->row(), 0,\r\n                     parentStrongRef.data());\r\n}\r\n\r\nint Model::rowCount(const QModelIndex &parent) const {\r\n  const TreeItem *parentItem = getItemFromIndex(parent);\r\n\r\n  if (!parentItem) return m_treeItems.size();\r\n\r\n  return parentItem->childCount();\r\n}\r\n\r\nbool Model::hasChildren(const QModelIndex &parent) const {\r\n  const TreeItem *parentItem = getItemFromIndex(parent);\r\n\r\n  if (!parentItem) return m_treeItems.size() > 0;\r\n\r\n  if (!parentItem->supportChildItems()) return false;\r\n\r\n  return parentItem->childCount() > 0;\r\n}\r\n\r\nQModelIndex Model::getIndexFromItem(QWeakPointer<TreeItem> item) {\r\n  if (!item) {\r\n    return QModelIndex();\r\n  }\r\n\r\n  auto sRef = item.toStrongRef();\r\n\r\n  if (!sRef) {\r\n    return QModelIndex();\r\n  }\r\n\r\n  if (!sRef->parent()) {\r\n    return index(sRef->row(), 0, QModelIndex());\r\n  }\r\n\r\n  m_rawPointers->insert(sRef.data(), item);\r\n  return createIndex(sRef->row(), 0, (void *)sRef.data());\r\n}\r\n\r\nvoid Model::onItemChanged(QWeakPointer<TreeItem> item) {\r\n  if (!item) return;  \r\n\r\n  auto index = getIndexFromItem(item);\r\n\r\n  if (!index.isValid()) return;\r\n\r\n  emit dataChanged(index, index);\r\n}\r\n\r\nvoid Model::beforeItemChildsUnloaded(QWeakPointer<TreeItem> item)\r\n{\r\n    if (!item) return;    \r\n\r\n    auto index = getIndexFromItem(item);\r\n\r\n    if (!index.isValid()) return;\r\n\r\n    auto itemPtr = item.toStrongRef();\r\n\r\n    if (!itemPtr || itemPtr->childCount() == 0)\r\n        return;\r\n\r\n    beginRemoveRows(index, 0, itemPtr->childCount() - 1);\r\n}\r\n\r\nvoid Model::beforeChildLoadedAtPos(QWeakPointer<TreeItem> item, int pos)\r\n{\r\n    if (!item) return;    \r\n\r\n    auto index = getIndexFromItem(item);\r\n\r\n    if (!index.isValid()) return;\r\n\r\n    beginInsertRows(index, pos, pos);\r\n}\r\n\r\nvoid Model::beforeChildLoaded(QWeakPointer<TreeItem> item, int count)\r\n{\r\n    if (!item) return;    \r\n\r\n    auto index = getIndexFromItem(item);\r\n\r\n    if (!index.isValid()) return;\r\n\r\n    auto treeItem = item.toStrongRef();\r\n\r\n    if (!treeItem) return;\r\n\r\n    beginInsertRows(index, treeItem->getAllChilds().size(),\r\n                    treeItem->getAllChilds().size() + count - 1);\r\n}\r\n\r\nvoid Model::childLoaded(QWeakPointer<TreeItem> item)\r\n{\r\n    if (!item) return;    \r\n\r\n    auto index = getIndexFromItem(item);\r\n\r\n    if (!index.isValid()) return;\r\n\r\n    endInsertRows();\r\n}\r\n\r\nvoid Model::beforeItemChildRemoved(QWeakPointer<TreeItem> item, int row)\r\n{\r\n    if (!item) return;    \r\n\r\n    auto index = getIndexFromItem(item);\r\n\r\n    if (!index.isValid()) return;    \r\n\r\n    beginRemoveRows(index, row, row);\r\n}\r\n\r\nvoid Model::itemChildRemoved(QWeakPointer<TreeItem> childItem)\r\n{\r\n    if (!childItem) return;    \r\n\r\n    endRemoveRows();\r\n}\r\n\r\nvoid Model::expandItem(QWeakPointer<TreeItem> item) {\r\n  if (!item) return;  \r\n\r\n  auto index = getIndexFromItem(item);\r\n\r\n  if (!index.isValid()) return;\r\n\r\n  emit expand(index);\r\n}\r\n\r\nvoid Model::iterateAllChilds(QSharedPointer<TreeItem> item, QList<PendingIndexChange>& pendingChanges)\r\n{\r\n    if (!item->isExpanded()) {\r\n        return;\r\n    }\r\n\r\n    for (long rowIndex = 0; rowIndex < item->childCount(); rowIndex++) {\r\n      auto child = item->child(rowIndex);\r\n\r\n      pendingChanges.append({child, getIndexFromItem(child)});\r\n      iterateAllChilds(child, pendingChanges);\r\n    }\r\n}\r\n\r\nvoid Model::beforeItemLayoutChanged(QWeakPointer<TreeItem> item) {\r\n  if (!item) return;  \r\n\r\n  auto itemS = item.toStrongRef();\r\n\r\n  auto index = getIndexFromItem(item);\r\n\r\n  if (!index.isValid()) return;\r\n\r\n  if (m_pendingChanges.contains(item)) {\r\n      m_pendingChanges.remove(item);\r\n  }  \r\n\r\n  emit layoutAboutToBeChanged({}, QAbstractItemModel::VerticalSortHint);\r\n\r\n  QList<PendingIndexChange> pendingChanges;\r\n\r\n  iterateAllChilds(itemS, pendingChanges);  \r\n\r\n  m_pendingChanges[item] = pendingChanges;\r\n}\r\n\r\nvoid Model::itemLayoutChanged(QWeakPointer<TreeItem> item) {\r\n  if (!item) return;  \r\n\r\n  auto itemS = item.toStrongRef();\r\n\r\n  auto index = getIndexFromItem(item);\r\n\r\n  if (!index.isValid()) return;\r\n\r\n  if (!m_pendingChanges.contains(item)) {\r\n      qWarning() << \"Item \" << item << \" doesnt have pending layout changes\";\r\n      return;\r\n  }\r\n\r\n  auto changeIndexes = m_pendingChanges.take(item);\r\n\r\n  while (changeIndexes.size() > 0) {\r\n      auto change = changeIndexes.takeFirst();\r\n\r\n      auto child = change.first.toStrongRef();\r\n\r\n      if (!child) {\r\n          qDebug() << \"Layout change: Child was removed. Skipping\";\r\n          continue;\r\n      }\r\n\r\n      auto from = change.second;\r\n      auto to = getIndexFromItem(child);      \r\n      changePersistentIndex(from, to);\r\n  }  \r\n\r\n  emit layoutChanged({}, QAbstractItemModel::VerticalSortHint);\r\n\r\n  for (long rowIndex = 0; rowIndex < itemS->childCount(); rowIndex++) {\r\n    auto child = itemS->child(rowIndex);\r\n    auto childIndex = getIndexFromItem(child);\r\n\r\n    emit dataChanged(childIndex, childIndex);\r\n  }\r\n}\r\n\r\nvoid Model::setMetadata(const QModelIndex &index, const QString &metaKey,\r\n                        QVariant value) {\r\n  TreeItem *item = getItemFromIndex(index);\r\n\r\n  if (item == nullptr) return;\r\n\r\n  item->setMetadata(metaKey, value);\r\n}\r\n\r\nvoid Model::sendEvent(const QModelIndex &index, QString event) {\r\n  TreeItem *item = getItemFromIndex(index);\r\n\r\n  if (!item)\r\n      return;\r\n\r\n  item->handleEvent(event);\r\n}\r\n\r\nint Model::size() { return m_treeItems.size(); }\r\n\r\nvoid Model::setExpanded(const QModelIndex &index) {\r\n  TreeItem *item = getItemFromIndex(index);\r\n\r\n  if (!item) return;\r\n\r\n  item->setExpanded(true);\r\n\r\n  if (item->type() != \"namespace\") return;\r\n\r\n  expandedNamespaces.insert(item->getFullPath());\r\n}\r\n\r\nvoid Model::setCollapsed(const QModelIndex &index) {\r\n  TreeItem *item = getItemFromIndex(index);\r\n\r\n  if (!item) return;\r\n\r\n  QTimer::singleShot(10, [item]() {\r\n      if (item) item->setExpanded(false);\r\n  });\r\n\r\n  if (item->type() != \"namespace\") return;\r\n\r\n  if (expandedNamespaces.contains(item->getFullPath())) {\r\n    expandedNamespaces.remove(item->getFullPath());\r\n\r\n    QMutableSetIterator<QByteArray> it(expandedNamespaces);\r\n    while (it.hasNext()) {\r\n        if (it.next().startsWith(item->getFullPath()))\r\n            it.remove();\r\n    }\r\n  }\r\n}\r\n\r\nvoid Model::collapseRootItems()\r\n{\r\n    for (const auto &item : qAsConst(m_treeItems)) {\r\n\r\n        auto server = item.dynamicCast<SortableTreeItem>();\r\n\r\n        if (!server)\r\n            continue;\r\n\r\n        server->unload();\r\n    }\r\n}\r\n\r\nvoid Model::dropItemAt(const QModelIndex &index, const QModelIndex &at)\r\n{\r\n    if (!(index.isValid() && at.isValid()))\r\n        return;\r\n\r\n    auto item = getItemFromIndex(index);\r\n    auto targetItem = getItemFromIndex(at);\r\n\r\n    if (!(item && targetItem)) return;\r\n\r\n    if (!(item->type() == \"server\"\r\n          && targetItem->type() == \"server_group\"\r\n          && (!item->parent() || item->parent() != targetItem->getSelf()))) {\r\n        return;\r\n    }\r\n\r\n    int targetIndex = targetItem->childCount();\r\n\r\n    auto srcParent = QModelIndex();\r\n    auto targetParent = at;\r\n\r\n    if (item->parent()) {\r\n        srcParent = getIndexFromItem(item->parent());\r\n    }\r\n\r\n    bool res = beginMoveRows(srcParent, index.row(), index.row(),\r\n                             targetParent, targetIndex);\r\n\r\n    if (!res) {\r\n        return;\r\n    }\r\n\r\n    auto findRootItem = [](TreeItem* t, QList<QSharedPointer<TreeItem>> treeItems) {\r\n        for (auto rI : treeItems) {\r\n            if (t == rI.data())\r\n                return rI;\r\n        }\r\n\r\n        return QSharedPointer<TreeItem>();\r\n    };\r\n\r\n    QSharedPointer<TreeItem> srv;\r\n\r\n    if (item->parent()) {\r\n        srv = findRootItem(item, item->parent().toStrongRef()->getAllChilds());\r\n\r\n        auto sourceGroup = item->parent().toStrongRef().dynamicCast<ServerGroup>();\r\n\r\n        if (!sourceGroup) {\r\n            qDebug() << \"invalid source group\";\r\n            return;\r\n        }\r\n\r\n        sourceGroup->removeConnection(srv);\r\n    } else {\r\n      srv = findRootItem(item, m_treeItems);\r\n      m_treeItems.removeAll(srv);\r\n    }\r\n\r\n    endMoveRows();\r\n\r\n    auto targetGroup = findRootItem(targetItem, m_treeItems).dynamicCast<ServerGroup>();\r\n\r\n    if (!targetGroup) {\r\n        qDebug() << \"invalid target group\";\r\n        return;\r\n    }\r\n\r\n    targetGroup->addServer(srv);\r\n\r\n    auto srvItem = srv.dynamicCast<ServerItem>();\r\n\r\n    if (!srvItem) {\r\n        qDebug() << \"invalid srv item\";\r\n        return;\r\n    }\r\n\r\n    srvItem->setParent(targetGroup.toWeakRef());\r\n\r\n    emit layoutAboutToBeChanged();\r\n    emit layoutChanged();\r\n}\r\n\r\nvoid Model::applyGroupChanges()\r\n{\r\n    emit layoutAboutToBeChanged();\r\n\r\n    // TBD\r\n\r\n    emit layoutChanged();\r\n}\r\n\r\nvoid Model::addRootItem(QSharedPointer<SortableTreeItem> item) {\r\n  if (item.isNull()) return;\r\n\r\n  int insertIndex = m_treeItems.size();\r\n\r\n  beginInsertRows(QModelIndex(), insertIndex, insertIndex);\r\n\r\n  item->setRow(insertIndex);\r\n\r\n  m_treeItems.push_back(item);\r\n\r\n  endInsertRows();\r\n\r\n  if (item->isExpanded() && item->childCount() > 0) {\r\n      QTimer::singleShot(100, this, [this, item]() {\r\n        if (item)\r\n            emit expand(getIndexFromItem(item));\r\n      });\r\n  }\r\n}\r\n\r\nvoid Model::removeRootItem(QSharedPointer<TreeItem> item) {\r\n  if (!item) return;  \r\n\r\n  beginRemoveRows(QModelIndex(), item->row(), item->row());\r\n  m_treeItems.removeAll(item);\r\n  endRemoveRows();\r\n}\r\n"
  },
  {
    "path": "src/modules/connections-tree/model.h",
    "content": "﻿#pragma once\r\n#include <QAbstractItemModel>\r\n#include <QDebug>\r\n#include <QList>\r\n#include <QQuickImageProvider>\r\n#include <QSharedPointer>\r\n#include <QVariant>\r\n\r\n#include \"items/sortabletreeitem.h\"\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass ServerItem;\r\nclass AbstractNamespaceItem;\r\n\r\nclass Model : public QAbstractItemModel {\r\n  Q_OBJECT\r\n public:\r\n  enum Roles {\r\n    itemMetaData = Qt::UserRole + 1\r\n  };\r\n\r\n public:\r\n  explicit Model(QObject *parent = 0);\r\n\r\n  QVariant data(const QModelIndex &index, int role) const override;\r\n\r\n  QHash<int, QByteArray> roleNames() const override;\r\n\r\n  Qt::ItemFlags flags(const QModelIndex &index) const override;\r\n\r\n  QModelIndex index(int row, int column, const QModelIndex &parent) const override;\r\n\r\n  QModelIndex parent(const QModelIndex &index) const override;\r\n\r\n  int rowCount(const QModelIndex &parent = QModelIndex()) const override;\r\n\r\n  bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;\r\n\r\n  inline int columnCount(const QModelIndex &parent = QModelIndex()) const override {\r\n    Q_UNUSED(parent);\r\n    return 1;\r\n  }\r\n\r\n  inline TreeItem *getItemFromIndex(const QModelIndex &index) const {\r\n    if (!index.isValid()) return nullptr;\r\n    if (index.model() != this) return nullptr;\r\n\r\n    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());\r\n    if (!item || !m_rawPointers->contains(item)) return nullptr;\r\n\r\n    if (!m_rawPointers->value(item)) {\r\n      m_rawPointers->remove(item);\r\n      return nullptr;\r\n    }\r\n    return item;\r\n  }\r\n\r\n  QModelIndex getIndexFromItem(QWeakPointer<TreeItem>);\r\n\r\n  QSet<QByteArray> expandedNamespaces;\r\n\r\n signals:\r\n  void expand(const QModelIndex &index);\r\n\r\n  void error(const QString &err);\r\n\r\n  void itemChanged(QWeakPointer<TreeItem> item);\r\n\r\n public:\r\n  void beforeItemChildsUnloaded(QWeakPointer<TreeItem> item);  \r\n\r\n  void beforeChildLoadedAtPos(QWeakPointer<TreeItem> item, int pos);\r\n\r\n  void beforeChildLoaded(QWeakPointer<TreeItem> item, int count);\r\n\r\n  void childLoaded(QWeakPointer<TreeItem> item);\r\n\r\n  void beforeItemChildRemoved(QWeakPointer<TreeItem> item, int row);\r\n\r\n  void itemChildRemoved(QWeakPointer<TreeItem> childItem);\r\n\r\n  void expandItem(QWeakPointer<TreeItem> item);\r\n\r\n  void beforeItemLayoutChanged(QWeakPointer<TreeItem> item);\r\n\r\n  void itemLayoutChanged(QWeakPointer<TreeItem> item);\r\n\r\n public slots:\r\n  void onItemChanged(QWeakPointer<TreeItem> item);\r\n\r\n  void setMetadata(const QModelIndex &index, const QString &metaKey,\r\n                   QVariant value);\r\n\r\n  void sendEvent(const QModelIndex &index, QString event);\r\n\r\n  virtual int size();\r\n\r\n  void setExpanded(const QModelIndex &index);\r\n\r\n  void setCollapsed(const QModelIndex &index);\r\n\r\n  void collapseRootItems();\r\n\r\n  void dropItemAt(const QModelIndex &index, const QModelIndex &at);\r\n\r\n  virtual void applyGroupChanges();\r\n\r\n protected:\r\n  void addRootItem(QSharedPointer<ConnectionsTree::SortableTreeItem> item);\r\n\r\n  void removeRootItem(QSharedPointer<TreeItem> item);\r\n\r\n  typedef QPair<QWeakPointer<TreeItem>, QModelIndex> PendingIndexChange;\r\n\r\n  void iterateAllChilds(QSharedPointer<TreeItem> item, QList<PendingIndexChange> &pendingChanges);  \r\n\r\n protected:\r\n  QList<QSharedPointer<TreeItem>> m_treeItems;\r\n  QSharedPointer<QHash<TreeItem *, QWeakPointer<TreeItem>>> m_rawPointers; \r\n  QHash<QSharedPointer<TreeItem>, QList<PendingIndexChange>> m_pendingChanges;\r\n};\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/operations.h",
    "content": "#pragma once\r\n#include <qredisclient/connection.h>\r\n\r\n#include <QFuture>\r\n#include <QMap>\r\n#include <QSharedPointer>\r\n#include <QString>\r\n#include <QStringList>\r\n#include <functional>\r\n\r\n#include \"exception.h\"\r\n#include \"modules/common/callbackwithowner.h\"\r\n\r\nnamespace Console {\r\nclass Operations;\r\n}\r\n\r\nnamespace ConnectionsTree {\r\n\r\nclass KeyItem;\r\nclass NamespaceItem;\r\nclass AbstractNamespaceItem;\r\nclass DatabaseItem;\r\nclass TreeItem;\r\n\r\nclass Operations {\r\n  ADD_EXCEPTION\r\n public:\r\n  /**\r\n   * List of databases with keys counters\r\n   * @emit databesesLoaded\r\n   **/\r\n  using DbMapping = QMap<int, int>;\r\n  using GetDatabasesCallback =\r\n      CallbackWithOwner<TreeItem, DbMapping, const QString&>;\r\n\r\n  virtual QFuture<void> getDatabases(QSharedPointer<GetDatabasesCallback>) = 0;\r\n\r\n  /**\r\n   * @brief loadNamespaceItems\r\n   * @param dbIndex\r\n   * @param filter\r\n   * @param callback\r\n   */\r\n  using LoadNamespaceItemsCallback =\r\n      CallbackWithOwner<TreeItem, const RedisClient::Connection::RawKeysList&,\r\n                        const QString&>;\r\n\r\n  virtual void loadNamespaceItems(uint dbIndex, const QString& filter,\r\n                                  QSharedPointer<LoadNamespaceItemsCallback>) = 0;\r\n\r\n  /**\r\n   * Cancel all operations & close connection\r\n   * @brief disconnect\r\n   */\r\n  virtual void disconnect() = 0;\r\n\r\n  /**\r\n    Cancel all operations & reconnect\r\n   * @brief resetConnection\r\n   */\r\n  virtual void resetConnection() = 0;\r\n\r\n  /**\r\n   * @brief getNamespaceSeparator\r\n   * @return\r\n   */\r\n  virtual QString getNamespaceSeparator() = 0;\r\n\r\n  virtual QString iconColor() = 0;\r\n\r\n  virtual QString defaultFilter() = 0;\r\n\r\n  virtual QVariantMap getFilterHistory() = 0;\r\n\r\n  virtual QString connectionName() const = 0;\r\n\r\n  virtual void openKeyTab(QSharedPointer<KeyItem> key, bool openInNewTab) = 0;\r\n\r\n  virtual void openConsoleTab(int dbIndex = 0) = 0;\r\n\r\n  using OpenNewKeyDialogCallback = CallbackWithOwner<TreeItem>;\r\n\r\n  virtual void openNewKeyDialog(int dbIndex, QSharedPointer<OpenNewKeyDialogCallback> callback,\r\n                                QString keyPrefix = QString()) = 0;\r\n\r\n  virtual void openServerStats() = 0;\r\n\r\n  virtual void duplicateConnection() = 0;\r\n\r\n  virtual void notifyDbWasUnloaded(int dbIndex) = 0;\r\n\r\n  using DeleteDbKeyCallback = CallbackWithOwner<TreeItem, const QString&>;\r\n\r\n  virtual void deleteDbKey(ConnectionsTree::KeyItem& key,\r\n                           QSharedPointer<DeleteDbKeyCallback> callback) = 0;\r\n\r\n  virtual void deleteDbKeys(ConnectionsTree::DatabaseItem& db) = 0;\r\n\r\n  virtual void deleteDbNamespace(ConnectionsTree::NamespaceItem& ns) = 0;\r\n\r\n  virtual void setTTL(ConnectionsTree::AbstractNamespaceItem& ns) = 0;\r\n\r\n  virtual void copyKeys(ConnectionsTree::AbstractNamespaceItem& ns) = 0;\r\n\r\n  virtual void importKeysFromRdb(ConnectionsTree::DatabaseItem& ns) = 0;\r\n\r\n  using FlushDbCallback = CallbackWithOwner<TreeItem, const QString&>;\r\n\r\n  virtual void flushDb(int dbIndex, QSharedPointer<FlushDbCallback> callback) = 0;\r\n\r\n  using OpenKeyIfExistsCallback = CallbackWithOwner<TreeItem, const QString&, bool>;\r\n\r\n  virtual void openKeyIfExists(\r\n      const QByteArray& key,\r\n      QSharedPointer<ConnectionsTree::DatabaseItem> parent,\r\n      QSharedPointer<OpenKeyIfExistsCallback> callback) = 0;\r\n\r\n  virtual QString mode() = 0;\r\n\r\n  virtual bool isConnected() const = 0;\r\n\r\n  virtual QFuture<bool> connectionSupportsMemoryOperations() = 0;\r\n\r\n  using GetUsedMemoryCallback = CallbackWithOwner<TreeItem, qlonglong>;\r\n\r\n  virtual void getUsedMemory(\r\n      const QList<QByteArray>& keys, int dbIndex,\r\n      QSharedPointer<GetUsedMemoryCallback> result,\r\n      QSharedPointer<GetUsedMemoryCallback> progress) = 0;\r\n\r\n  virtual ~Operations() {}\r\n};\r\n}  // namespace ConnectionsTree\r\n"
  },
  {
    "path": "src/modules/connections-tree/utils.cpp",
    "content": "#include \"utils.h\"\n#include <QMessageBox>\n#include <QKeySequence>\n#include <qtranslator.h>\n\nvoid ConnectionsTree::confirmAction(QWidget *parent, const QString &msg,\n                                    std::function<void ()> action, QString title)\n{\n    QMessageBox::StandardButton reply = QMessageBox::question(parent, title,\n                                                              msg, QMessageBox::Yes|QMessageBox::No);\n    if (reply == QMessageBox::Yes) {\n        action();\n    }\n}\n"
  },
  {
    "path": "src/modules/connections-tree/utils.h",
    "content": "#pragma once\n#include <functional>\n#include <QMenu>\n#include <QWidget>\n\nnamespace ConnectionsTree {\n\nvoid confirmAction(QWidget* parent, const QString& msg, std::function<void()> action,\n                   QString title = \"Confirm action\");\n\n}\n"
  },
  {
    "path": "src/modules/console/autocompletemodel.cpp",
    "content": "#include \"autocompletemodel.h\"\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QJsonArray>\n#include <QDebug>\n#include <QFile>\n\nConsole::AutocompleteModel::AutocompleteModel()\n{\n    QFile commandsResource(\"://commands.json\");\n    if (!commandsResource.open(QIODevice::ReadOnly)) {\n        qWarning() << \"Cannot load list of redis commands. Autocomplete in redis console wont work.\";\n        return;\n    }\n\n    QByteArray commandsJsonRaw = commandsResource.readAll();\n\n    QJsonDocument commandsJson = QJsonDocument::fromJson(commandsJsonRaw);\n\n    if (commandsJson.isEmpty() || !commandsJson.isArray()) {\n        qWarning() << \"Invalid commands.json\" << commandsJson;\n        return;\n    }\n\n    auto commands = commandsJson.array();\n\n    for (auto command : commands) {\n        auto cmd = command.toObject();\n\n        m_commands.append(\n            CommandInfo {\n                cmd[\"cmd\"].toString(),\n                cmd[\"arguments\"].toString(),\n                cmd[\"summary\"].toString(),\n                cmd[\"since\"].toString()\n            }\n        );\n    }\n}\n\nQModelIndex Console::AutocompleteModel::index(int row, int column, const QModelIndex &parent) const\n{\n    Q_UNUSED(parent);\n\n    if (row < 0 || column < 0)\n        return QModelIndex();\n\n    return createIndex(row, 0);\n}\n\nint Console::AutocompleteModel::rowCount(const QModelIndex &parent) const\n{\n    return m_commands.size();\n}\n\nQVariant Console::AutocompleteModel::data(const QModelIndex &index, int role) const\n{\n    if (!isIndexValid(index))\n        return QVariant();\n\n    auto command = m_commands[index.row()];\n\n    switch (role) {\n        case name: return command.name;\n        case arguments: return command.arguments;\n        case summary: return command.summary;\n        case since: return command.since;\n    }\n\n    return QVariant();\n}\n\nQHash<int, QByteArray> Console::AutocompleteModel::roleNames() const\n{\n    QHash<int, QByteArray> roles;\n    roles[name] = \"name\";\n    roles[arguments] = \"arguments\";\n    roles[summary] = \"summary\";\n    roles[since] = \"since\";\n    return roles;\n}\n\nQVariantMap Console::AutocompleteModel::getRow(int i)\n{\n    if (!isRowIndexValid(i))\n        return QVariantMap();\n\n    return getRowRaw(i);\n}\n"
  },
  {
    "path": "src/modules/console/autocompletemodel.h",
    "content": "#pragma once\n#include <QList>\n#include <QHash>\n#include \"common/baselistmodel.h\"\n\nnamespace Console {\n\n    class AutocompleteModel : public BaseListModel\n    {\n        Q_OBJECT\n\n    public:\n        enum Roles {\n            name = Qt::UserRole + 1,\n            arguments,\n            summary,\n            since\n        };\n\n        AutocompleteModel();\n\n        QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const override;\n\n        int rowCount(const QModelIndex& parent = QModelIndex()) const override;\n\n        QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;\n\n        QHash<int, QByteArray> roleNames() const override;\n\n        Q_INVOKABLE QVariantMap getRow(int i);\n\n    private:\n        struct CommandInfo {\n            QString name;\n            QString arguments;\n            QString summary;\n            QString since;\n        };\n\n        QList<CommandInfo> m_commands;\n    };\n}\n"
  },
  {
    "path": "src/modules/console/consolemodel.cpp",
    "content": "#include \"consolemodel.h\"\n#include <qredisclient/redisclient.h>\n#include <QCoreApplication>\n\nusing namespace Console;\n\nModel::Model(QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n             QList<QByteArray> initCmd)\n    : TabModel(connection, dbIndex), m_current_db(dbIndex) {\n  QObject::connect(this, &TabModel::initialized, [this, initCmd]() {\n    if (m_connection->mode() == RedisClient::Connection::Mode::Cluster) {\n      emit addOutput(\n          QCoreApplication::translate(\"RESP\", \"Connected to cluster.\\n\"),\n          \"complete\");\n    } else {\n      emit addOutput(QCoreApplication::translate(\"RESP\", \"Connected.\\n\"),\n                     \"complete\");\n    }\n\n    updatePrompt(true);\n\n    if (initCmd.size() > 0)\n        execCmd(initCmd);\n  });\n\n  QObject::connect(this, &TabModel::error, [this](const QString& msg) {\n    emit addOutput(msg, \"error\");\n  });\n}\n\nQString Model::getName() const { return m_connection->getConfig().name(); }\n\nvoid Model::executeCommand(const QString& cmd) {\n  return execCmd(RedisClient::Command::splitCommandString(cmd));\n}\n\nvoid Model::updatePrompt(bool showPrompt) {\n  if (m_connection->mode() == RedisClient::Connection::Mode::Cluster) {\n    emit changePrompt(QString(\"%1(%2:%3)>\")\n                          .arg(m_connection->getConfig().name())\n                          .arg(m_connection->getConfig().host())\n                          .arg(m_connection->getConfig().port()),\n                      showPrompt);\n  } else {\n    emit changePrompt(QString(\"%1:%2>\")\n                          .arg(m_connection->getConfig().name())\n                          .arg(m_current_db),\n                      showPrompt);\n  }\n}\n\nvoid Model::execCmd(QList<QByteArray> cmd)\n{\n    using namespace RedisClient;\n\n    Command command(cmd, m_current_db);\n\n    if (command.isSubscriptionCommand() || command.isMonitorCommand()) {\n      emit addOutput(\n          QCoreApplication::translate(\n              \"RESP\",\n              \"Switch to %1 mode. Close console tab to stop listen for \"\n              \"messages.\").arg(command.isSubscriptionCommand()? \"Pub/Sub\": \"Monitor\"),\n          \"part\");\n\n      command.setCallBack(this, [this](Response result, QString err) {\n        if (!err.isEmpty()) {\n          emit addOutput(\n              QCoreApplication::translate(\"RESP\", \"Subscribe error: %1\").arg(err),\n              \"error\");\n          return;\n        }\n\n        QVariant value = result.value();\n        emit addOutput(RedisClient::Response::valueToHumanReadString(value).replace(\"\\r\\n\", \"\\n\"),\n                       \"part\");\n      });\n\n      m_connection->command(command);\n    } else {\n        bool isSelect = command.isSelectCommand();\n        command.setCallBack(this, [this, isSelect](Response result, QString err) {\n          if (!err.isEmpty()) {\n              emit addOutput(QCoreApplication::translate(\"RESP\", \"Connection error: \") +\n                                 QString(err),\n                             \"error\");\n            return;\n          }\n\n          if (isSelect || m_connection->mode() == RedisClient::Connection::Mode::Cluster) {\n            m_current_db = m_connection->dbIndex();\n            updatePrompt(false);\n          }\n\n          QVariant value = result.value();\n          emit addOutput(RedisClient::Response::valueToHumanReadString(value).replace(\"\\r\\n\", \"\\n\"),\n                         \"complete\");\n        });\n\n        m_connection->command(command);\n    }\n}\n"
  },
  {
    "path": "src/modules/console/consolemodel.h",
    "content": "#pragma once\n#include \"common/tabviewmodel.h\"\n#include \"exception.h\"\n\nnamespace Console {\n\nclass Model : public TabModel {\n  Q_OBJECT\n  ADD_EXCEPTION\n public:\n  Model(QSharedPointer<RedisClient::Connection> connection, int dbIndex, QList<QByteArray> initCmd);\n\n  QString getName() const override;\n\n public slots:\n  void executeCommand(const QString &);\n\n signals:\n  void changePrompt(const QString &text, bool showPrompt);\n  void addOutput(const QString &text, QString resultType);\n\n private:\n  int m_current_db;\n\n private:\n  void updatePrompt(bool showPrompt);\n  void execCmd(QList<QByteArray> cmd);\n};\n}  // namespace Console\n"
  },
  {
    "path": "src/modules/exception.h",
    "content": "#pragma once\r\n\r\n#include <stdexcept>\r\n#include <QString>\r\n\r\n#define ADD_EXCEPTION \\\r\npublic: struct Exception : public std::runtime_error { \\\r\n    Exception(const QString &err) : std::runtime_error(err.toStdString()) {} \\\r\n};\r\n"
  },
  {
    "path": "src/modules/extension-server/client/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.2)\n\nproject(client)\nset(CMAKE_VERBOSE_MAKEFILE ON)\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\nset(CMAKE_AUTOMOC ON)\n\nif (MSVC)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /W4\")\nelse ()\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fPIC -Wall -Wno-unused-variable\")\nendif ()\n\nfind_package(Qt5Core REQUIRED)\nfind_package(Qt5Network REQUIRED)\n\nadd_library(${PROJECT_NAME}\n    OAIDataFormatter.cpp\n    OAIDecodePayload.cpp\n    OAIEncodePayload.cpp\n    OAIInline_response_400.cpp\n    OAIDefaultApi.cpp\n    OAIHelpers.cpp\n    OAIHttpRequest.cpp\n    OAIHttpFileElement.cpp\n    OAIOauth.cpp\n)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Network)\nif(NOT APPLE)\n  target_link_libraries(${PROJECT_NAME} PRIVATE ssl crypto)\nendif()\n\nset_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14)\nset_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)\nset_property(TARGET ${PROJECT_NAME} PROPERTY CXX_EXTENSIONS OFF)\n\ninstall(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIDataFormatter.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include \"OAIDataFormatter.h\"\n\n#include <QDebug>\n#include <QJsonArray>\n#include <QJsonDocument>\n#include <QObject>\n\n#include \"OAIHelpers.h\"\n\nnamespace RespExtServer {\n\nOAIDataFormatter::OAIDataFormatter(QString json) {\n    this->initializeModel();\n    this->fromJson(json);\n}\n\nOAIDataFormatter::OAIDataFormatter() {\n    this->initializeModel();\n}\n\nOAIDataFormatter::~OAIDataFormatter() {}\n\nvoid OAIDataFormatter::initializeModel() {\n\n    m_id_isSet = false;\n    m_id_isValid = false;\n\n    m_name_isSet = false;\n    m_name_isValid = false;\n\n    m_key_types_isSet = false;\n    m_key_types_isValid = false;\n\n    m_magic_header_isSet = false;\n    m_magic_header_isValid = false;\n\n    m_read_only_isSet = false;\n    m_read_only_isValid = false;\n}\n\nvoid OAIDataFormatter::fromJson(QString jsonString) {\n    QByteArray array(jsonString.toStdString().c_str());\n    QJsonDocument doc = QJsonDocument::fromJson(array);\n    QJsonObject jsonObject = doc.object();\n    this->fromJsonObject(jsonObject);\n}\n\nvoid OAIDataFormatter::fromJsonObject(QJsonObject json) {\n\n    m_id_isValid = ::RespExtServer::fromJsonValue(id, json[QString(\"id\")]);\n    m_id_isSet = !json[QString(\"id\")].isNull() && m_id_isValid;\n\n    m_name_isValid = ::RespExtServer::fromJsonValue(name, json[QString(\"name\")]);\n    m_name_isSet = !json[QString(\"name\")].isNull() && m_name_isValid;\n\n    m_key_types_isValid = ::RespExtServer::fromJsonValue(key_types, json[QString(\"key-types\")]);\n    m_key_types_isSet = !json[QString(\"key-types\")].isNull() && m_key_types_isValid;\n\n    m_magic_header_isValid = ::RespExtServer::fromJsonValue(magic_header, json[QString(\"magic-header\")]);\n    m_magic_header_isSet = !json[QString(\"magic-header\")].isNull() && m_magic_header_isValid;\n\n    m_read_only_isValid = ::RespExtServer::fromJsonValue(read_only, json[QString(\"read-only\")]);\n    m_read_only_isSet = !json[QString(\"read-only\")].isNull() && m_read_only_isValid;\n}\n\nQString OAIDataFormatter::asJson() const {\n    QJsonObject obj = this->asJsonObject();\n    QJsonDocument doc(obj);\n    QByteArray bytes = doc.toJson();\n    return QString(bytes);\n}\n\nQJsonObject OAIDataFormatter::asJsonObject() const {\n    QJsonObject obj;\n    if (m_id_isSet) {\n        obj.insert(QString(\"id\"), ::RespExtServer::toJsonValue(id));\n    }\n    if (m_name_isSet) {\n        obj.insert(QString(\"name\"), ::RespExtServer::toJsonValue(name));\n    }\n    if (m_key_types_isSet) {\n        obj.insert(QString(\"key-types\"), ::RespExtServer::toJsonValue(key_types));\n    }\n    if (m_magic_header_isSet) {\n        obj.insert(QString(\"magic-header\"), ::RespExtServer::toJsonValue(magic_header));\n    }\n    if (m_read_only_isSet) {\n        obj.insert(QString(\"read-only\"), ::RespExtServer::toJsonValue(read_only));\n    }\n    return obj;\n}\n\nQString OAIDataFormatter::getId() const {\n    return id;\n}\nvoid OAIDataFormatter::setId(const QString &id) {\n    this->id = id;\n    this->m_id_isSet = true;\n}\n\nbool OAIDataFormatter::is_id_Set() const{\n    return m_id_isSet;\n}\n\nbool OAIDataFormatter::is_id_Valid() const{\n    return m_id_isValid;\n}\n\nQString OAIDataFormatter::getName() const {\n    return name;\n}\nvoid OAIDataFormatter::setName(const QString &name) {\n    this->name = name;\n    this->m_name_isSet = true;\n}\n\nbool OAIDataFormatter::is_name_Set() const{\n    return m_name_isSet;\n}\n\nbool OAIDataFormatter::is_name_Valid() const{\n    return m_name_isValid;\n}\n\nQString OAIDataFormatter::getKeyTypes() const {\n    return key_types;\n}\nvoid OAIDataFormatter::setKeyTypes(const QString &key_types) {\n    this->key_types = key_types;\n    this->m_key_types_isSet = true;\n}\n\nbool OAIDataFormatter::is_key_types_Set() const{\n    return m_key_types_isSet;\n}\n\nbool OAIDataFormatter::is_key_types_Valid() const{\n    return m_key_types_isValid;\n}\n\nQString OAIDataFormatter::getMagicHeader() const {\n    return magic_header;\n}\nvoid OAIDataFormatter::setMagicHeader(const QString &magic_header) {\n    this->magic_header = magic_header;\n    this->m_magic_header_isSet = true;\n}\n\nbool OAIDataFormatter::is_magic_header_Set() const{\n    return m_magic_header_isSet;\n}\n\nbool OAIDataFormatter::is_magic_header_Valid() const{\n    return m_magic_header_isValid;\n}\n\nbool OAIDataFormatter::isReadOnly() const {\n    return read_only;\n}\nvoid OAIDataFormatter::setReadOnly(const bool &read_only) {\n    this->read_only = read_only;\n    this->m_read_only_isSet = true;\n}\n\nbool OAIDataFormatter::is_read_only_Set() const{\n    return m_read_only_isSet;\n}\n\nbool OAIDataFormatter::is_read_only_Valid() const{\n    return m_read_only_isValid;\n}\n\nbool OAIDataFormatter::isSet() const {\n    bool isObjectUpdated = false;\n    do {\n        if (m_id_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n\n        if (m_name_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n\n        if (m_key_types_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n\n        if (m_magic_header_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n\n        if (m_read_only_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n    } while (false);\n    return isObjectUpdated;\n}\n\nbool OAIDataFormatter::isValid() const {\n    // only required properties are required for the object to be considered valid\n    return m_id_isValid && m_name_isValid && true;\n}\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIDataFormatter.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/*\n * OAIDataFormatter.h\n *\n * \n */\n\n#ifndef OAIDataFormatter_H\n#define OAIDataFormatter_H\n\n#include <QJsonObject>\n\n#include <QString>\n\n#include \"OAIEnum.h\"\n#include \"OAIObject.h\"\n\nnamespace RespExtServer {\n\nclass OAIDataFormatter : public OAIObject {\npublic:\n    OAIDataFormatter();\n    OAIDataFormatter(QString json);\n    ~OAIDataFormatter() override;\n\n    QString asJson() const override;\n    QJsonObject asJsonObject() const override;\n    void fromJsonObject(QJsonObject json) override;\n    void fromJson(QString jsonString) override;\n\n    QString getId() const;\n    void setId(const QString &id);\n    bool is_id_Set() const;\n    bool is_id_Valid() const;\n\n    QString getName() const;\n    void setName(const QString &name);\n    bool is_name_Set() const;\n    bool is_name_Valid() const;\n\n    QString getKeyTypes() const;\n    void setKeyTypes(const QString &key_types);\n    bool is_key_types_Set() const;\n    bool is_key_types_Valid() const;\n\n    QString getMagicHeader() const;\n    void setMagicHeader(const QString &magic_header);\n    bool is_magic_header_Set() const;\n    bool is_magic_header_Valid() const;\n\n    bool isReadOnly() const;\n    void setReadOnly(const bool &read_only);\n    bool is_read_only_Set() const;\n    bool is_read_only_Valid() const;\n\n    virtual bool isSet() const override;\n    virtual bool isValid() const override;\n\nprivate:\n    void initializeModel();\n\n    QString id;\n    bool m_id_isSet;\n    bool m_id_isValid;\n\n    QString name;\n    bool m_name_isSet;\n    bool m_name_isValid;\n\n    QString key_types;\n    bool m_key_types_isSet;\n    bool m_key_types_isValid;\n\n    QString magic_header;\n    bool m_magic_header_isSet;\n    bool m_magic_header_isValid;\n\n    bool read_only;\n    bool m_read_only_isSet;\n    bool m_read_only_isValid;\n};\n\n} // namespace RespExtServer\n\nQ_DECLARE_METATYPE(RespExtServer::OAIDataFormatter)\n\n#endif // OAIDataFormatter_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIDecodePayload.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include \"OAIDecodePayload.h\"\n\n#include <QDebug>\n#include <QJsonArray>\n#include <QJsonDocument>\n#include <QObject>\n\n#include \"OAIHelpers.h\"\n\nnamespace RespExtServer {\n\nOAIDecodePayload::OAIDecodePayload(QString json) {\n    this->initializeModel();\n    this->fromJson(json);\n}\n\nOAIDecodePayload::OAIDecodePayload() {\n    this->initializeModel();\n}\n\nOAIDecodePayload::~OAIDecodePayload() {}\n\nvoid OAIDecodePayload::initializeModel() {\n\n    m_data_isSet = false;\n    m_data_isValid = false;\n\n    m_redis_key_name_isSet = false;\n    m_redis_key_name_isValid = false;\n\n    m_redis_key_type_isSet = false;\n    m_redis_key_type_isValid = false;\n}\n\nvoid OAIDecodePayload::fromJson(QString jsonString) {\n    QByteArray array(jsonString.toStdString().c_str());\n    QJsonDocument doc = QJsonDocument::fromJson(array);\n    QJsonObject jsonObject = doc.object();\n    this->fromJsonObject(jsonObject);\n}\n\nvoid OAIDecodePayload::fromJsonObject(QJsonObject json) {\n\n    m_data_isValid = ::RespExtServer::fromJsonValue(data, json[QString(\"data\")]);\n    m_data_isSet = !json[QString(\"data\")].isNull() && m_data_isValid;\n\n    m_redis_key_name_isValid = ::RespExtServer::fromJsonValue(redis_key_name, json[QString(\"redis-key-name\")]);\n    m_redis_key_name_isSet = !json[QString(\"redis-key-name\")].isNull() && m_redis_key_name_isValid;\n\n    m_redis_key_type_isValid = ::RespExtServer::fromJsonValue(redis_key_type, json[QString(\"redis-key-type\")]);\n    m_redis_key_type_isSet = !json[QString(\"redis-key-type\")].isNull() && m_redis_key_type_isValid;\n}\n\nQString OAIDecodePayload::asJson() const {\n    QJsonObject obj = this->asJsonObject();\n    QJsonDocument doc(obj);\n    QByteArray bytes = doc.toJson();\n    return QString(bytes);\n}\n\nQJsonObject OAIDecodePayload::asJsonObject() const {\n    QJsonObject obj;\n    if (m_data_isSet) {\n        obj.insert(QString(\"data\"), ::RespExtServer::toJsonValue(data));\n    }\n    if (m_redis_key_name_isSet) {\n        obj.insert(QString(\"redis-key-name\"), ::RespExtServer::toJsonValue(redis_key_name));\n    }\n    if (m_redis_key_type_isSet) {\n        obj.insert(QString(\"redis-key-type\"), ::RespExtServer::toJsonValue(redis_key_type));\n    }\n    return obj;\n}\n\nQString OAIDecodePayload::getData() const {\n    return data;\n}\nvoid OAIDecodePayload::setData(const QString &data) {\n    this->data = data;\n    this->m_data_isSet = true;\n}\n\nbool OAIDecodePayload::is_data_Set() const{\n    return m_data_isSet;\n}\n\nbool OAIDecodePayload::is_data_Valid() const{\n    return m_data_isValid;\n}\n\nQString OAIDecodePayload::getRedisKeyName() const {\n    return redis_key_name;\n}\nvoid OAIDecodePayload::setRedisKeyName(const QString &redis_key_name) {\n    this->redis_key_name = redis_key_name;\n    this->m_redis_key_name_isSet = true;\n}\n\nbool OAIDecodePayload::is_redis_key_name_Set() const{\n    return m_redis_key_name_isSet;\n}\n\nbool OAIDecodePayload::is_redis_key_name_Valid() const{\n    return m_redis_key_name_isValid;\n}\n\nQString OAIDecodePayload::getRedisKeyType() const {\n    return redis_key_type;\n}\nvoid OAIDecodePayload::setRedisKeyType(const QString &redis_key_type) {\n    this->redis_key_type = redis_key_type;\n    this->m_redis_key_type_isSet = true;\n}\n\nbool OAIDecodePayload::is_redis_key_type_Set() const{\n    return m_redis_key_type_isSet;\n}\n\nbool OAIDecodePayload::is_redis_key_type_Valid() const{\n    return m_redis_key_type_isValid;\n}\n\nbool OAIDecodePayload::isSet() const {\n    bool isObjectUpdated = false;\n    do {\n        if (m_data_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n\n        if (m_redis_key_name_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n\n        if (m_redis_key_type_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n    } while (false);\n    return isObjectUpdated;\n}\n\nbool OAIDecodePayload::isValid() const {\n    // only required properties are required for the object to be considered valid\n    return true;\n}\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIDecodePayload.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/*\n * OAIDecodePayload.h\n *\n * \n */\n\n#ifndef OAIDecodePayload_H\n#define OAIDecodePayload_H\n\n#include <QJsonObject>\n\n#include <QString>\n\n#include \"OAIEnum.h\"\n#include \"OAIObject.h\"\n\nnamespace RespExtServer {\n\nclass OAIDecodePayload : public OAIObject {\npublic:\n    OAIDecodePayload();\n    OAIDecodePayload(QString json);\n    ~OAIDecodePayload() override;\n\n    QString asJson() const override;\n    QJsonObject asJsonObject() const override;\n    void fromJsonObject(QJsonObject json) override;\n    void fromJson(QString jsonString) override;\n\n    QString getData() const;\n    void setData(const QString &data);\n    bool is_data_Set() const;\n    bool is_data_Valid() const;\n\n    QString getRedisKeyName() const;\n    void setRedisKeyName(const QString &redis_key_name);\n    bool is_redis_key_name_Set() const;\n    bool is_redis_key_name_Valid() const;\n\n    QString getRedisKeyType() const;\n    void setRedisKeyType(const QString &redis_key_type);\n    bool is_redis_key_type_Set() const;\n    bool is_redis_key_type_Valid() const;\n\n    virtual bool isSet() const override;\n    virtual bool isValid() const override;\n\nprivate:\n    void initializeModel();\n\n    QString data;\n    bool m_data_isSet;\n    bool m_data_isValid;\n\n    QString redis_key_name;\n    bool m_redis_key_name_isSet;\n    bool m_redis_key_name_isValid;\n\n    QString redis_key_type;\n    bool m_redis_key_type_isSet;\n    bool m_redis_key_type_isValid;\n};\n\n} // namespace RespExtServer\n\nQ_DECLARE_METATYPE(RespExtServer::OAIDecodePayload)\n\n#endif // OAIDecodePayload_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIDefaultApi.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include \"OAIDefaultApi.h\"\n#include \"OAIServerConfiguration.h\"\n#include <QJsonArray>\n#include <QJsonDocument>\n\nnamespace RespExtServer {\n\nOAIDefaultApi::OAIDefaultApi(const int timeOut)\n    : _timeOut(timeOut),\n      _manager(nullptr),\n      _isResponseCompressionEnabled(false),\n      _isRequestCompressionEnabled(false) {\n    initializeServerConfigs();\n}\n\nOAIDefaultApi::~OAIDefaultApi() {\n}\n\nvoid OAIDefaultApi::initializeServerConfigs() {\n    //Default server\n    QList<OAIServerConfiguration> defaultConf = QList<OAIServerConfiguration>();\n    //varying endpoint server\n    defaultConf.append(OAIServerConfiguration(\n    QUrl(\"/\"),\n    \"No description provided\",\n    QMap<QString, OAIServerVariable>()));\n    _serverConfigs.insert(\"dataFormattersGet\", defaultConf);\n    _serverIndices.insert(\"dataFormattersGet\", 0);\n    _serverConfigs.insert(\"dataFormattersIdDecodePost\", defaultConf);\n    _serverIndices.insert(\"dataFormattersIdDecodePost\", 0);\n    _serverConfigs.insert(\"dataFormattersIdEncodePost\", defaultConf);\n    _serverIndices.insert(\"dataFormattersIdEncodePost\", 0);\n}\n\n/**\n* returns 0 on success and -1, -2 or -3 on failure.\n* -1 when the variable does not exist and -2 if the value is not defined in the enum and -3 if the operation or server index is not found\n*/\nint OAIDefaultApi::setDefaultServerValue(int serverIndex, const QString &operation, const QString &variable, const QString &value) {\n    auto it = _serverConfigs.find(operation);\n    if (it != _serverConfigs.end() && serverIndex < it.value().size()) {\n      return _serverConfigs[operation][serverIndex].setDefaultValue(variable,value);\n    }\n    return -3;\n}\nvoid OAIDefaultApi::setServerIndex(const QString &operation, int serverIndex) {\n    if (_serverIndices.contains(operation) && serverIndex < _serverConfigs.find(operation).value().size()) {\n        _serverIndices[operation] = serverIndex;\n    }\n}\n\nvoid OAIDefaultApi::setApiKey(const QString &apiKeyName, const QString &apiKey) {\n    _apiKeys.insert(apiKeyName,apiKey);\n}\n\nvoid OAIDefaultApi::setBearerToken(const QString &token) {\n    _bearerToken = token;\n}\n\nvoid OAIDefaultApi::setUsername(const QString &username) {\n    _username = username;\n}\n\nvoid OAIDefaultApi::setPassword(const QString &password) {\n    _password = password;\n}\n\n\nvoid OAIDefaultApi::setTimeOut(const int timeOut) {\n    _timeOut = timeOut;\n}\n\nvoid OAIDefaultApi::setWorkingDirectory(const QString &path) {\n    _workingDirectory = path;\n}\n\nvoid OAIDefaultApi::setNetworkAccessManager(QNetworkAccessManager* manager) {\n    _manager = manager;\n}\n\n/**\n    * Appends a new ServerConfiguration to the config map for a specific operation.\n    * @param operation The id to the target operation.\n    * @param url A string that contains the URL of the server\n    * @param description A String that describes the server\n    * @param variables A map between a variable name and its value. The value is used for substitution in the server's URL template.\n    * returns the index of the new server config on success and -1 if the operation is not found\n    */\nint OAIDefaultApi::addServerConfiguration(const QString &operation, const QUrl &url, const QString &description, const QMap<QString, OAIServerVariable> &variables) {\n    if (_serverConfigs.contains(operation)) {\n        _serverConfigs[operation].append(OAIServerConfiguration(\n                    url,\n                    description,\n                    variables));\n        return _serverConfigs[operation].size()-1;\n    } else {\n        return -1;\n    }\n}\n\n/**\n    * Appends a new ServerConfiguration to the config map for a all operations and sets the index to that server.\n    * @param url A string that contains the URL of the server\n    * @param description A String that describes the server\n    * @param variables A map between a variable name and its value. The value is used for substitution in the server's URL template.\n    */\nvoid OAIDefaultApi::setNewServerForAllOperations(const QUrl &url, const QString &description, const QMap<QString, OAIServerVariable> &variables) {\n#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)\n    for (auto keyIt = _serverIndices.keyBegin(); keyIt != _serverIndices.keyEnd(); keyIt++) {\n        setServerIndex(*keyIt, addServerConfiguration(*keyIt, url, description, variables));\n    }\n#else\n    for (auto &e : _serverIndices.keys()) {\n        setServerIndex(e, addServerConfiguration(e, url, description, variables));\n    }\n#endif\n}\n\n/**\n    * Appends a new ServerConfiguration to the config map for an operations and sets the index to that server.\n    * @param URL A string that contains the URL of the server\n    * @param description A String that describes the server\n    * @param variables A map between a variable name and its value. The value is used for substitution in the server's URL template.\n    */\nvoid OAIDefaultApi::setNewServer(const QString &operation, const QUrl &url, const QString &description, const QMap<QString, OAIServerVariable> &variables) {\n    setServerIndex(operation, addServerConfiguration(operation, url, description, variables));\n}\n\nvoid OAIDefaultApi::addHeaders(const QString &key, const QString &value) {\n    _defaultHeaders.insert(key, value);\n}\n\nvoid OAIDefaultApi::enableRequestCompression() {\n    _isRequestCompressionEnabled = true;\n}\n\nvoid OAIDefaultApi::enableResponseCompression() {\n    _isResponseCompressionEnabled = true;\n}\n\nvoid OAIDefaultApi::abortRequests() {\n    emit abortRequestsSignal();\n}\n\nQString OAIDefaultApi::getParamStylePrefix(const QString &style) {\n    if (style == \"matrix\") {\n        return \";\";\n    } else if (style == \"label\") {\n        return \".\";\n    } else if (style == \"form\") {\n        return \"&\";\n    } else if (style == \"simple\") {\n        return \"\";\n    } else if (style == \"spaceDelimited\") {\n        return \"&\";\n    } else if (style == \"pipeDelimited\") {\n        return \"&\";\n    } else {\n        return \"none\";\n    }\n}\n\nQString OAIDefaultApi::getParamStyleSuffix(const QString &style) {\n    if (style == \"matrix\") {\n        return \"=\";\n    } else if (style == \"label\") {\n        return \"\";\n    } else if (style == \"form\") {\n        return \"=\";\n    } else if (style == \"simple\") {\n        return \"\";\n    } else if (style == \"spaceDelimited\") {\n        return \"=\";\n    } else if (style == \"pipeDelimited\") {\n        return \"=\";\n    } else {\n        return \"none\";\n    }\n}\n\nQString OAIDefaultApi::getParamStyleDelimiter(const QString &style, const QString &name, bool isExplode) {\n\n    if (style == \"matrix\") {\n        return (isExplode) ? \";\" + name + \"=\" : \",\";\n\n    } else if (style == \"label\") {\n        return (isExplode) ? \".\" : \",\";\n\n    } else if (style == \"form\") {\n        return (isExplode) ? \"&\" + name + \"=\" : \",\";\n\n    } else if (style == \"simple\") {\n        return \",\";\n    } else if (style == \"spaceDelimited\") {\n        return (isExplode) ? \"&\" + name + \"=\" : \" \";\n\n    } else if (style == \"pipeDelimited\") {\n        return (isExplode) ? \"&\" + name + \"=\" : \"|\";\n\n    } else if (style == \"deepObject\") {\n        return (isExplode) ? \"&\" : \"none\";\n\n    } else {\n        return \"none\";\n    }\n}\n\nvoid OAIDefaultApi::dataFormattersGet() {\n    QString fullPath = QString(_serverConfigs[\"dataFormattersGet\"][_serverIndices.value(\"dataFormattersGet\")].URL()+\"/data-formatters\");\n    \n    if (!_username.isEmpty() && !_password.isEmpty()) {\n        QByteArray b64;\n        b64.append(_username.toUtf8() + \":\" + _password.toUtf8());\n        addHeaders(\"Authorization\",\"Basic \" + b64.toBase64());\n    }\n    OAIHttpRequestWorker *worker = new OAIHttpRequestWorker(this, _manager);\n    worker->setTimeOut(_timeOut);\n    worker->setWorkingDirectory(_workingDirectory);\n    OAIHttpRequestInput input(fullPath, \"GET\");\n\n\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    for (auto keyValueIt = _defaultHeaders.keyValueBegin(); keyValueIt != _defaultHeaders.keyValueEnd(); keyValueIt++) {\n        input.headers.insert(keyValueIt->first, keyValueIt->second);\n    }\n#else\n    for (auto key : _defaultHeaders.keys()) {\n        input.headers.insert(key, _defaultHeaders[key]);\n    }\n#endif\n\n    connect(worker, &OAIHttpRequestWorker::on_execution_finished, this, &OAIDefaultApi::dataFormattersGetCallback);\n    connect(this, &OAIDefaultApi::abortRequestsSignal, worker, &QObject::deleteLater);\n    connect(worker, &QObject::destroyed, this, [this]() {\n        if (findChildren<OAIHttpRequestWorker*>().count() == 0) {\n            emit allPendingRequestsCompleted();\n        }\n    });\n\n    worker->execute(&input);\n}\n\nvoid OAIDefaultApi::dataFormattersGetCallback(OAIHttpRequestWorker *worker) {\n    QString error_str = worker->error_str;\n    QNetworkReply::NetworkError error_type = worker->error_type;\n\n    if (worker->error_type != QNetworkReply::NoError) {\n        error_str = QString(\"%1, %2\").arg(worker->error_str, QString(worker->response));\n    }\n    QList<OAIDataFormatter> output;\n    QString json(worker->response);\n    QByteArray array(json.toStdString().c_str());\n    QJsonDocument doc = QJsonDocument::fromJson(array);\n    QJsonArray jsonArray = doc.array();\n    foreach (QJsonValue obj, jsonArray) {\n        OAIDataFormatter val;\n        ::RespExtServer::fromJsonValue(val, obj);\n        output.append(val);\n    }\n    worker->deleteLater();\n\n    if (worker->error_type == QNetworkReply::NoError) {\n        emit dataFormattersGetSignal(output);\n        emit dataFormattersGetSignalFull(worker, output);\n    } else {\n        emit dataFormattersGetSignalE(output, error_type, error_str);\n        emit dataFormattersGetSignalEFull(worker, error_type, error_str);\n    }\n}\n\nvoid OAIDefaultApi::dataFormattersIdDecodePost(const QString &id, const ::RespExtServer::OptionalParam<OAIDecodePayload> &oai_decode_payload) {\n    QString fullPath = QString(_serverConfigs[\"dataFormattersIdDecodePost\"][_serverIndices.value(\"dataFormattersIdDecodePost\")].URL()+\"/data-formatters/{id}/decode\");\n    \n    if (!_username.isEmpty() && !_password.isEmpty()) {\n        QByteArray b64;\n        b64.append(_username.toUtf8() + \":\" + _password.toUtf8());\n        addHeaders(\"Authorization\",\"Basic \" + b64.toBase64());\n    }\n    \n    {\n        QString idPathParam(\"{\");\n        idPathParam.append(\"id\").append(\"}\");\n        QString pathPrefix, pathSuffix, pathDelimiter;\n        QString pathStyle = \"simple\";\n        if (pathStyle == \"\")\n            pathStyle = \"simple\";\n        pathPrefix = getParamStylePrefix(pathStyle);\n        pathSuffix = getParamStyleSuffix(pathStyle);\n        pathDelimiter = getParamStyleDelimiter(pathStyle, \"id\", false);\n        QString paramString = (pathStyle == \"matrix\") ? pathPrefix+\"id\"+pathSuffix : pathPrefix;\n        fullPath.replace(idPathParam, paramString+QUrl::toPercentEncoding(::RespExtServer::toStringValue(id)));\n    }\n    OAIHttpRequestWorker *worker = new OAIHttpRequestWorker(this, _manager);\n    worker->setTimeOut(_timeOut);\n    worker->setWorkingDirectory(_workingDirectory);\n    OAIHttpRequestInput input(fullPath, \"POST\");\n\n    if (oai_decode_payload.hasValue()){\n\n        QByteArray output = oai_decode_payload.value().asJson().toUtf8();\n        input.request_body.append(output);\n    }\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    for (auto keyValueIt = _defaultHeaders.keyValueBegin(); keyValueIt != _defaultHeaders.keyValueEnd(); keyValueIt++) {\n        input.headers.insert(keyValueIt->first, keyValueIt->second);\n    }\n#else\n    for (auto key : _defaultHeaders.keys()) {\n        input.headers.insert(key, _defaultHeaders[key]);\n    }\n#endif\n\n    connect(worker, &OAIHttpRequestWorker::on_execution_finished, this, &OAIDefaultApi::dataFormattersIdDecodePostCallback);\n    connect(this, &OAIDefaultApi::abortRequestsSignal, worker, &QObject::deleteLater);\n    connect(worker, &QObject::destroyed, this, [this]() {\n        if (findChildren<OAIHttpRequestWorker*>().count() == 0) {\n            emit allPendingRequestsCompleted();\n        }\n    });\n\n    worker->execute(&input);\n}\n\nvoid OAIDefaultApi::dataFormattersIdDecodePostCallback(OAIHttpRequestWorker *worker) {\n    QString error_str = worker->error_str;\n    QNetworkReply::NetworkError error_type = worker->error_type;\n\n    if (worker->error_type != QNetworkReply::NoError) {\n        error_str = QString(\"%1, %2\").arg(worker->error_str, QString(worker->response));\n    }\n    QString output;\n    ::RespExtServer::fromStringValue(QString(worker->response), output);\n    worker->deleteLater();\n\n    if (worker->error_type == QNetworkReply::NoError) {\n        emit dataFormattersIdDecodePostSignal(output);\n        emit dataFormattersIdDecodePostSignalFull(worker, output);\n    } else {\n        emit dataFormattersIdDecodePostSignalE(output, error_type, error_str);\n        emit dataFormattersIdDecodePostSignalEFull(worker, error_type, error_str);\n    }\n}\n\nvoid OAIDefaultApi::dataFormattersIdEncodePost(const QString &id, const ::RespExtServer::OptionalParam<OAIEncodePayload> &oai_encode_payload) {\n    QString fullPath = QString(_serverConfigs[\"dataFormattersIdEncodePost\"][_serverIndices.value(\"dataFormattersIdEncodePost\")].URL()+\"/data-formatters/{id}/encode\");\n    \n    if (!_username.isEmpty() && !_password.isEmpty()) {\n        QByteArray b64;\n        b64.append(_username.toUtf8() + \":\" + _password.toUtf8());\n        addHeaders(\"Authorization\",\"Basic \" + b64.toBase64());\n    }\n    \n    {\n        QString idPathParam(\"{\");\n        idPathParam.append(\"id\").append(\"}\");\n        QString pathPrefix, pathSuffix, pathDelimiter;\n        QString pathStyle = \"simple\";\n        if (pathStyle == \"\")\n            pathStyle = \"simple\";\n        pathPrefix = getParamStylePrefix(pathStyle);\n        pathSuffix = getParamStyleSuffix(pathStyle);\n        pathDelimiter = getParamStyleDelimiter(pathStyle, \"id\", false);\n        QString paramString = (pathStyle == \"matrix\") ? pathPrefix+\"id\"+pathSuffix : pathPrefix;\n        fullPath.replace(idPathParam, paramString+QUrl::toPercentEncoding(::RespExtServer::toStringValue(id)));\n    }\n    OAIHttpRequestWorker *worker = new OAIHttpRequestWorker(this, _manager);\n    worker->setTimeOut(_timeOut);\n    worker->setWorkingDirectory(_workingDirectory);\n    OAIHttpRequestInput input(fullPath, \"POST\");\n\n    if (oai_encode_payload.hasValue()){\n\n        QByteArray output = oai_encode_payload.value().asJson().toUtf8();\n        input.request_body.append(output);\n    }\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    for (auto keyValueIt = _defaultHeaders.keyValueBegin(); keyValueIt != _defaultHeaders.keyValueEnd(); keyValueIt++) {\n        input.headers.insert(keyValueIt->first, keyValueIt->second);\n    }\n#else\n    for (auto key : _defaultHeaders.keys()) {\n        input.headers.insert(key, _defaultHeaders[key]);\n    }\n#endif\n\n    connect(worker, &OAIHttpRequestWorker::on_execution_finished, this, &OAIDefaultApi::dataFormattersIdEncodePostCallback);\n    connect(this, &OAIDefaultApi::abortRequestsSignal, worker, &QObject::deleteLater);\n    connect(worker, &QObject::destroyed, this, [this]() {\n        if (findChildren<OAIHttpRequestWorker*>().count() == 0) {\n            emit allPendingRequestsCompleted();\n        }\n    });\n\n    worker->execute(&input);\n}\n\nvoid OAIDefaultApi::dataFormattersIdEncodePostCallback(OAIHttpRequestWorker *worker) {\n    QString error_str = worker->error_str;\n    QNetworkReply::NetworkError error_type = worker->error_type;\n\n    if (worker->error_type != QNetworkReply::NoError) {\n        error_str = QString(\"%1, %2\").arg(worker->error_str, QString(worker->response));\n    }\n    QString output;\n    ::RespExtServer::fromStringValue(QString(worker->response), output);\n    worker->deleteLater();\n\n    if (worker->error_type == QNetworkReply::NoError) {\n        emit dataFormattersIdEncodePostSignal(output);\n        emit dataFormattersIdEncodePostSignalFull(worker, output);\n    } else {\n        emit dataFormattersIdEncodePostSignalE(output, error_type, error_str);\n        emit dataFormattersIdEncodePostSignalEFull(worker, error_type, error_str);\n    }\n}\n\nvoid OAIDefaultApi::tokenAvailable(){\n  \n    oauthToken token; \n    switch (_OauthMethod) {\n    case 1: //implicit flow\n        token = _implicitFlow.getToken(_latestScope.join(\" \"));\n        if(token.isValid()){\n            _latestInput.headers.insert(\"Authorization\", \"Bearer \" + token.getToken());\n            _latestWorker->execute(&_latestInput);\n        }else{\n            _implicitFlow.removeToken(_latestScope.join(\" \"));\n            qDebug() << \"Could not retreive a valid token\";\n        }\n        break;\n    case 2: //authorization flow\n        token = _authFlow.getToken(_latestScope.join(\" \"));\n        if(token.isValid()){\n            _latestInput.headers.insert(\"Authorization\", \"Bearer \" + token.getToken());\n            _latestWorker->execute(&_latestInput);\n        }else{\n            _authFlow.removeToken(_latestScope.join(\" \"));    \n            qDebug() << \"Could not retreive a valid token\";\n        }\n        break;\n    case 3: //client credentials flow\n        token = _credentialFlow.getToken(_latestScope.join(\" \"));\n        if(token.isValid()){\n            _latestInput.headers.insert(\"Authorization\", \"Bearer \" + token.getToken());\n            _latestWorker->execute(&_latestInput);\n        }else{\n            _credentialFlow.removeToken(_latestScope.join(\" \"));    \n            qDebug() << \"Could not retreive a valid token\";\n        }\n        break;\n    case 4: //resource owner password flow\n        token = _passwordFlow.getToken(_latestScope.join(\" \"));\n        if(token.isValid()){\n            _latestInput.headers.insert(\"Authorization\", \"Bearer \" + token.getToken());\n            _latestWorker->execute(&_latestInput);\n        }else{\n            _credentialFlow.removeToken(_latestScope.join(\" \"));    \n            qDebug() << \"Could not retreive a valid token\";\n        }\n        break;\n    default:\n        qDebug() << \"No Oauth method set!\";\n        break;\n    }\n}\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIDefaultApi.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#ifndef OAI_OAIDefaultApi_H\n#define OAI_OAIDefaultApi_H\n\n#include \"OAIHelpers.h\"\n#include \"OAIHttpRequest.h\"\n#include \"OAIServerConfiguration.h\"\n#include \"OAIOauth.h\"\n\n#include \"OAIDataFormatter.h\"\n#include \"OAIDecodePayload.h\"\n#include \"OAIEncodePayload.h\"\n#include \"OAIInline_response_400.h\"\n#include <QString>\n\n#include <QObject>\n#include <QByteArray>\n#include <QStringList>\n#include <QList>\n#include <QNetworkAccessManager>\n\nnamespace RespExtServer {\n\nclass OAIDefaultApi : public QObject {\n    Q_OBJECT\n\npublic:\n    OAIDefaultApi(const int timeOut = 0);\n    ~OAIDefaultApi();\n\n    void initializeServerConfigs();\n    int setDefaultServerValue(int serverIndex,const QString &operation, const QString &variable,const QString &val);\n    void setServerIndex(const QString &operation, int serverIndex);\n    void setApiKey(const QString &apiKeyName, const QString &apiKey);\n    void setBearerToken(const QString &token);\n    void setUsername(const QString &username);\n    void setPassword(const QString &password);\n    void setTimeOut(const int timeOut);\n    void setWorkingDirectory(const QString &path);\n    void setNetworkAccessManager(QNetworkAccessManager* manager);\n    int addServerConfiguration(const QString &operation, const QUrl &url, const QString &description = \"\", const QMap<QString, OAIServerVariable> &variables = QMap<QString, OAIServerVariable>());\n    void setNewServerForAllOperations(const QUrl &url, const QString &description = \"\", const QMap<QString, OAIServerVariable> &variables =  QMap<QString, OAIServerVariable>());\n    void setNewServer(const QString &operation, const QUrl &url, const QString &description = \"\", const QMap<QString, OAIServerVariable> &variables =  QMap<QString, OAIServerVariable>());\n    void addHeaders(const QString &key, const QString &value);\n    void enableRequestCompression();\n    void enableResponseCompression();\n    void abortRequests();\n    QString getParamStylePrefix(const QString &style);\n    QString getParamStyleSuffix(const QString &style);\n    QString getParamStyleDelimiter(const QString &style, const QString &name, bool isExplode);\n\n\n    void dataFormattersGet();\n\n    /**\n    * @param[in]  id QString [required]\n    * @param[in]  oai_decode_payload OAIDecodePayload [optional]\n    */\n    void dataFormattersIdDecodePost(const QString &id, const ::RespExtServer::OptionalParam<OAIDecodePayload> &oai_decode_payload = ::RespExtServer::OptionalParam<OAIDecodePayload>());\n\n    /**\n    * @param[in]  id QString [required]\n    * @param[in]  oai_encode_payload OAIEncodePayload [optional]\n    */\n    void dataFormattersIdEncodePost(const QString &id, const ::RespExtServer::OptionalParam<OAIEncodePayload> &oai_encode_payload = ::RespExtServer::OptionalParam<OAIEncodePayload>());\n\n\nprivate:\n    QMap<QString,int> _serverIndices;\n    QMap<QString,QList<OAIServerConfiguration>> _serverConfigs;\n    QMap<QString, QString> _apiKeys;\n    QString _bearerToken;\n    QString _username;\n    QString _password;\n    int _timeOut;\n    QString _workingDirectory;\n    QNetworkAccessManager* _manager;\n    QMap<QString, QString> _defaultHeaders;\n    bool _isResponseCompressionEnabled;\n    bool _isRequestCompressionEnabled;\n    OAIHttpRequestInput _latestInput;\n    OAIHttpRequestWorker *_latestWorker;\n    QStringList _latestScope;\n    OauthCode _authFlow;\n    OauthImplicit _implicitFlow;\n    OauthCredentials _credentialFlow;\n    OauthPassword _passwordFlow;\n    int _OauthMethod = 0;\n\n    void dataFormattersGetCallback(OAIHttpRequestWorker *worker);\n    void dataFormattersIdDecodePostCallback(OAIHttpRequestWorker *worker);\n    void dataFormattersIdEncodePostCallback(OAIHttpRequestWorker *worker);\n\nsignals:\n\n    void dataFormattersGetSignal(QList<OAIDataFormatter> summary);\n    void dataFormattersIdDecodePostSignal(QString summary);\n    void dataFormattersIdEncodePostSignal(QString summary);\n\n    void dataFormattersGetSignalFull(OAIHttpRequestWorker *worker, QList<OAIDataFormatter> summary);\n    void dataFormattersIdDecodePostSignalFull(OAIHttpRequestWorker *worker, QString summary);\n    void dataFormattersIdEncodePostSignalFull(OAIHttpRequestWorker *worker, QString summary);\n\n    void dataFormattersGetSignalE(QList<OAIDataFormatter> summary, QNetworkReply::NetworkError error_type, QString error_str);\n    void dataFormattersIdDecodePostSignalE(QString summary, QNetworkReply::NetworkError error_type, QString error_str);\n    void dataFormattersIdEncodePostSignalE(QString summary, QNetworkReply::NetworkError error_type, QString error_str);\n\n    void dataFormattersGetSignalEFull(OAIHttpRequestWorker *worker, QNetworkReply::NetworkError error_type, QString error_str);\n    void dataFormattersIdDecodePostSignalEFull(OAIHttpRequestWorker *worker, QNetworkReply::NetworkError error_type, QString error_str);\n    void dataFormattersIdEncodePostSignalEFull(OAIHttpRequestWorker *worker, QNetworkReply::NetworkError error_type, QString error_str);\n\n    void abortRequestsSignal();\n    void allPendingRequestsCompleted();\n\npublic slots:\n    void tokenAvailable();\n    \n};\n\n} // namespace RespExtServer\n#endif\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIEncodePayload.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include \"OAIEncodePayload.h\"\n\n#include <QDebug>\n#include <QJsonArray>\n#include <QJsonDocument>\n#include <QObject>\n\n#include \"OAIHelpers.h\"\n\nnamespace RespExtServer {\n\nOAIEncodePayload::OAIEncodePayload(QString json) {\n    this->initializeModel();\n    this->fromJson(json);\n}\n\nOAIEncodePayload::OAIEncodePayload() {\n    this->initializeModel();\n}\n\nOAIEncodePayload::~OAIEncodePayload() {}\n\nvoid OAIEncodePayload::initializeModel() {\n\n    m_data_isSet = false;\n    m_data_isValid = false;\n\n    m_metadata_isSet = false;\n    m_metadata_isValid = false;\n}\n\nvoid OAIEncodePayload::fromJson(QString jsonString) {\n    QByteArray array(jsonString.toStdString().c_str());\n    QJsonDocument doc = QJsonDocument::fromJson(array);\n    QJsonObject jsonObject = doc.object();\n    this->fromJsonObject(jsonObject);\n}\n\nvoid OAIEncodePayload::fromJsonObject(QJsonObject json) {\n\n    m_data_isValid = ::RespExtServer::fromJsonValue(data, json[QString(\"data\")]);\n    m_data_isSet = !json[QString(\"data\")].isNull() && m_data_isValid;\n\n    m_metadata_isValid = ::RespExtServer::fromJsonValue(metadata, json[QString(\"metadata\")]);\n    m_metadata_isSet = !json[QString(\"metadata\")].isNull() && m_metadata_isValid;\n}\n\nQString OAIEncodePayload::asJson() const {\n    QJsonObject obj = this->asJsonObject();\n    QJsonDocument doc(obj);\n    QByteArray bytes = doc.toJson();\n    return QString(bytes);\n}\n\nQJsonObject OAIEncodePayload::asJsonObject() const {\n    QJsonObject obj;\n    if (m_data_isSet) {\n        obj.insert(QString(\"data\"), ::RespExtServer::toJsonValue(data));\n    }\n    if (m_metadata_isSet) {\n        obj.insert(QString(\"metadata\"), ::RespExtServer::toJsonValue(metadata));\n    }\n    return obj;\n}\n\nQString OAIEncodePayload::getData() const {\n    return data;\n}\nvoid OAIEncodePayload::setData(const QString &data) {\n    this->data = data;\n    this->m_data_isSet = true;\n}\n\nbool OAIEncodePayload::is_data_Set() const{\n    return m_data_isSet;\n}\n\nbool OAIEncodePayload::is_data_Valid() const{\n    return m_data_isValid;\n}\n\nOAIObject OAIEncodePayload::getMetadata() const {\n    return metadata;\n}\nvoid OAIEncodePayload::setMetadata(const OAIObject &metadata) {\n    this->metadata = metadata;\n    this->m_metadata_isSet = true;\n}\n\nbool OAIEncodePayload::is_metadata_Set() const{\n    return m_metadata_isSet;\n}\n\nbool OAIEncodePayload::is_metadata_Valid() const{\n    return m_metadata_isValid;\n}\n\nbool OAIEncodePayload::isSet() const {\n    bool isObjectUpdated = false;\n    do {\n        if (m_data_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n\n        if (m_metadata_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n    } while (false);\n    return isObjectUpdated;\n}\n\nbool OAIEncodePayload::isValid() const {\n    // only required properties are required for the object to be considered valid\n    return true;\n}\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIEncodePayload.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/*\n * OAIEncodePayload.h\n *\n * \n */\n\n#ifndef OAIEncodePayload_H\n#define OAIEncodePayload_H\n\n#include <QJsonObject>\n\n#include \"OAIObject.h\"\n#include <QString>\n\n#include \"OAIEnum.h\"\n#include \"OAIObject.h\"\n\nnamespace RespExtServer {\n\nclass OAIEncodePayload : public OAIObject {\npublic:\n    OAIEncodePayload();\n    OAIEncodePayload(QString json);\n    ~OAIEncodePayload() override;\n\n    QString asJson() const override;\n    QJsonObject asJsonObject() const override;\n    void fromJsonObject(QJsonObject json) override;\n    void fromJson(QString jsonString) override;\n\n    QString getData() const;\n    void setData(const QString &data);\n    bool is_data_Set() const;\n    bool is_data_Valid() const;\n\n    OAIObject getMetadata() const;\n    void setMetadata(const OAIObject &metadata);\n    bool is_metadata_Set() const;\n    bool is_metadata_Valid() const;\n\n    virtual bool isSet() const override;\n    virtual bool isValid() const override;\n\nprivate:\n    void initializeModel();\n\n    QString data;\n    bool m_data_isSet;\n    bool m_data_isValid;\n\n    OAIObject metadata;\n    bool m_metadata_isSet;\n    bool m_metadata_isValid;\n};\n\n} // namespace RespExtServer\n\nQ_DECLARE_METATYPE(RespExtServer::OAIEncodePayload)\n\n#endif // OAIEncodePayload_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIEnum.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#ifndef OAI_ENUM_H\n#define OAI_ENUM_H\n\n#include <QJsonValue>\n#include <QMetaType>\n#include <QString>\n\nnamespace RespExtServer {\n\nclass OAIEnum {\npublic:\n    OAIEnum() {}\n\n    OAIEnum(QString jsonString) {\n        fromJson(jsonString);\n    }\n\n    virtual ~OAIEnum() {}\n\n    virtual QJsonValue asJsonValue() const {\n        return QJsonValue(jstr);\n    }\n\n    virtual QString asJson() const {\n        return jstr;\n    }\n\n    virtual void fromJson(QString jsonString) {\n        jstr = jsonString;\n    }\n\n    virtual void fromJsonValue(QJsonValue jval) {\n        jstr = jval.toString();\n    }\n\n    virtual bool isSet() const {\n        return false;\n    }\n\n    virtual bool isValid() const {\n        return true;\n    }\n\nprivate:\n    QString jstr;\n};\n\n} // namespace RespExtServer\n\nQ_DECLARE_METATYPE(RespExtServer::OAIEnum)\n\n#endif // OAI_ENUM_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIHelpers.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include <QDebug>\n#include <QJsonParseError>\n#include \"OAIHelpers.h\"\n\nnamespace RespExtServer {\n\nclass OAISerializerSettings {\npublic:\n    struct CustomDateTimeFormat{\n        bool isStringSet = false;\n        QString formatString;\n        bool isEnumSet = false;\n        Qt::DateFormat formatEnum;\n    };\n\n    static CustomDateTimeFormat getCustomDateTimeFormat() {\n        return getInstance()->customDateTimeFormat;\n    }\n\n    static void setDateTimeFormatString(const QString &dtFormat){\n        getInstance()->customDateTimeFormat.isStringSet = true;\n        getInstance()->customDateTimeFormat.isEnumSet = false;\n        getInstance()->customDateTimeFormat.formatString = dtFormat;\n    }\n\n    static void setDateTimeFormatEnum(const Qt::DateFormat &dtFormat){\n        getInstance()->customDateTimeFormat.isEnumSet = true;\n        getInstance()->customDateTimeFormat.isStringSet = false;\n        getInstance()->customDateTimeFormat.formatEnum = dtFormat;\n    }\n\n    static OAISerializerSettings *getInstance(){\n        if(instance == nullptr){\n            instance = new OAISerializerSettings();\n        }\n        return instance;\n    }\n\nprivate:\n    explicit OAISerializerSettings(){\n        instance = this;\n        customDateTimeFormat.isStringSet = false;\n        customDateTimeFormat.isEnumSet = false;\n    }\n    static OAISerializerSettings *instance;\n    CustomDateTimeFormat customDateTimeFormat;\n};\n\nOAISerializerSettings * OAISerializerSettings::instance = nullptr;\n\nbool setDateTimeFormat(const QString &dateTimeFormat){\n    bool success = false;\n    auto dt = QDateTime::fromString(QDateTime::currentDateTime().toString(dateTimeFormat), dateTimeFormat);\n    if (dt.isValid()) {\n        success = true;\n        OAISerializerSettings::setDateTimeFormatString(dateTimeFormat);\n    }\n    return success;\n}\n\nbool setDateTimeFormat(const Qt::DateFormat &dateTimeFormat){\n    bool success = false;\n    auto dt = QDateTime::fromString(QDateTime::currentDateTime().toString(dateTimeFormat), dateTimeFormat);\n    if (dt.isValid()) {\n        success = true;\n        OAISerializerSettings::setDateTimeFormatEnum(dateTimeFormat);\n    }\n    return success;\n}\n\nQString toStringValue(const QString &value) {\n    return value;\n}\n\nQString toStringValue(const QDateTime &value) {\n    if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isStringSet) {\n        return value.toString(OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatString);\n    }\n\n    if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isEnumSet) {\n        return value.toString(OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatEnum);\n    }\n\n    // ISO 8601\n    return value.toString(Qt::ISODate);\n}\n\nQString toStringValue(const QByteArray &value) {\n    return QString(value);\n}\n\nQString toStringValue(const QDate &value) {\n    // ISO 8601\n    return value.toString(Qt::DateFormat::ISODate);\n}\n\nQString toStringValue(const qint32 &value) {\n    return QString::number(value);\n}\n\nQString toStringValue(const qint64 &value) {\n    return QString::number(value);\n}\n\nQString toStringValue(const bool &value) {\n    return QString(value ? \"true\" : \"false\");\n}\n\nQString toStringValue(const float &value) {\n    return QString::number(static_cast<double>(value));\n}\n\nQString toStringValue(const double &value) {\n    return QString::number(value);\n}\n\nQString toStringValue(const OAIObject &value) {\n    return value.asJson();\n}\n\nQString toStringValue(const OAIEnum &value) {\n    return value.asJson();\n}\n\nQString toStringValue(const OAIHttpFileElement &value) {\n    return value.asJson();\n}\n\nQJsonValue toJsonValue(const QString &value) {\n    return QJsonValue(value);\n}\n\nQJsonValue toJsonValue(const QDateTime &value) {\n    if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isStringSet) {\n        return QJsonValue(value.toString(OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatString));\n    }\n\n    if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isEnumSet) {\n        return QJsonValue(value.toString(OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatEnum));\n    }\n\n    // ISO 8601\n    return QJsonValue(value.toString(Qt::ISODate));\n}\n\nQJsonValue toJsonValue(const QByteArray &value) {\n    return QJsonValue(QString(value.toBase64()));\n}\n\nQJsonValue toJsonValue(const QDate &value) {\n    return QJsonValue(value.toString(Qt::ISODate));\n}\n\nQJsonValue toJsonValue(const qint32 &value) {\n    return QJsonValue(value);\n}\n\nQJsonValue toJsonValue(const qint64 &value) {\n    return QJsonValue(value);\n}\n\nQJsonValue toJsonValue(const bool &value) {\n    return QJsonValue(value);\n}\n\nQJsonValue toJsonValue(const float &value) {\n    return QJsonValue(static_cast<double>(value));\n}\n\nQJsonValue toJsonValue(const double &value) {\n    return QJsonValue(value);\n}\n\nQJsonValue toJsonValue(const OAIObject &value) {\n    return value.asJsonObject();\n}\n\nQJsonValue toJsonValue(const OAIEnum &value) {\n    return value.asJsonValue();\n}\n\nQJsonValue toJsonValue(const OAIHttpFileElement &value) {\n    return value.asJsonValue();\n}\n\nbool fromStringValue(const QString &inStr, QString &value) {\n    value.clear();\n    value.append(inStr);\n    return !inStr.isEmpty();\n}\n\nbool fromStringValue(const QString &inStr, QDateTime &value) {\n    if (inStr.isEmpty()) {\n        return false;\n    } else {\n       QDateTime dateTime;\n        if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isStringSet) {\n            dateTime = QDateTime::fromString(inStr, OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatString);\n        } else if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isEnumSet) {\n            dateTime = QDateTime::fromString(inStr, OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatEnum);\n        } else {\n            dateTime = QDateTime::fromString(inStr, Qt::ISODate);\n        }\n\n        if (dateTime.isValid()) {\n            value.setDate(dateTime.date());\n            value.setTime(dateTime.time());\n        } else {\n            qDebug() << \"DateTime is invalid\";\n        }\n        return dateTime.isValid();\n    }\n}\n\nbool fromStringValue(const QString &inStr, QByteArray &value) {\n    if (inStr.isEmpty()) {\n        return false;\n    } else {\n        value.clear();\n        value.append(inStr.toUtf8());\n        return value.count() > 0;\n    }\n}\n\nbool fromStringValue(const QString &inStr, QDate &value) {\n    if (inStr.isEmpty()) {\n        return false;\n    } else {\n        auto date = QDate::fromString(inStr, Qt::DateFormat::ISODate);\n        if (date.isValid()) {\n            value.setDate(date.year(), date.month(), date.day());\n        } else {\n            qDebug() << \"Date is invalid\";\n        }\n        return date.isValid();\n    }\n}\n\nbool fromStringValue(const QString &inStr, qint32 &value) {\n    bool ok = false;\n    value = QVariant(inStr).toInt(&ok);\n    return ok;\n}\n\nbool fromStringValue(const QString &inStr, qint64 &value) {\n    bool ok = false;\n    value = QVariant(inStr).toLongLong(&ok);\n    return ok;\n}\n\nbool fromStringValue(const QString &inStr, bool &value) {\n    value = QVariant(inStr).toBool();\n    return ((inStr == \"true\") || (inStr == \"false\"));\n}\n\nbool fromStringValue(const QString &inStr, float &value) {\n    bool ok = false;\n    value = QVariant(inStr).toFloat(&ok);\n    return ok;\n}\n\nbool fromStringValue(const QString &inStr, double &value) {\n    bool ok = false;\n    value = QVariant(inStr).toDouble(&ok);\n    return ok;\n}\n\nbool fromStringValue(const QString &inStr, OAIObject &value)\n{\n    QJsonParseError err;\n    QJsonDocument::fromJson(inStr.toUtf8(),&err);\n    if ( err.error == QJsonParseError::NoError ){\n        value.fromJson(inStr);\n        return true;\n    }\n    return false;\n}\n\nbool fromStringValue(const QString &inStr, OAIEnum &value) {\n    value.fromJson(inStr);\n    return true;\n}\n\nbool fromStringValue(const QString &inStr, OAIHttpFileElement &value) {\n    return value.fromStringValue(inStr);\n}\n\nbool fromJsonValue(QString &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (!jval.isUndefined() && !jval.isNull()) {\n        if (jval.isString()) {\n            value = jval.toString();\n        } else if (jval.isBool()) {\n            value = jval.toBool() ? \"true\" : \"false\";\n        } else if (jval.isDouble()) {\n            value = QString::number(jval.toDouble());\n        } else {\n            ok = false;\n        }\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(QDateTime &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (!jval.isUndefined() && !jval.isNull() && jval.isString()) {\n        if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isStringSet) {\n            value = QDateTime::fromString(jval.toString(), OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatString);\n        } else if (OAISerializerSettings::getInstance()->getCustomDateTimeFormat().isEnumSet) {\n            value = QDateTime::fromString(jval.toString(), OAISerializerSettings::getInstance()->getCustomDateTimeFormat().formatEnum);\n        } else {\n            value = QDateTime::fromString(jval.toString(), Qt::ISODate);\n        }\n        ok = value.isValid();\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(QByteArray &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (!jval.isUndefined() && !jval.isNull() && jval.isString()) {\n        value = QByteArray::fromBase64(QByteArray::fromStdString(jval.toString().toStdString()));\n        ok = value.size() > 0;\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(QDate &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (!jval.isUndefined() && !jval.isNull() && jval.isString()) {\n        value = QDate::fromString(jval.toString(), Qt::ISODate);\n        ok = value.isValid();\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(qint32 &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (!jval.isUndefined() && !jval.isNull() && !jval.isObject() && !jval.isArray()) {\n        value = jval.toInt();\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(qint64 &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (!jval.isUndefined() && !jval.isNull() && !jval.isObject() && !jval.isArray()) {\n        value = jval.toVariant().toLongLong();\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(bool &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (jval.isBool()) {\n        value = jval.toBool();\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(float &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (jval.isDouble()) {\n        value = static_cast<float>(jval.toDouble());\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(double &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (jval.isDouble()) {\n        value = jval.toDouble();\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(OAIObject &value, const QJsonValue &jval) {\n    bool ok = true;\n    if (jval.isObject()) {\n        value.fromJsonObject(jval.toObject());\n        ok = value.isValid();\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\nbool fromJsonValue(OAIEnum &value, const QJsonValue &jval) {\n    value.fromJsonValue(jval);\n    return true;\n}\n\nbool fromJsonValue(OAIHttpFileElement &value, const QJsonValue &jval) {\n    return value.fromJsonValue(jval);\n}\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIHelpers.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#ifndef OAI_HELPERS_H\n#define OAI_HELPERS_H\n\n#include <QByteArray>\n#include <QDate>\n#include <QDateTime>\n#include <QJsonArray>\n#include <QJsonObject>\n#include <QJsonValue>\n#include <QList>\n#include <QMap>\n#include <QSet>\n#include <QVariant>\n\n#include \"OAIEnum.h\"\n#include \"OAIHttpFileElement.h\"\n#include \"OAIObject.h\"\n\nnamespace RespExtServer {\n\ntemplate <typename T>\nclass OptionalParam {\npublic:\n    T m_Value;\n    bool m_hasValue;\npublic:\n    OptionalParam(){\n        m_hasValue = false;\n    }\n    OptionalParam(const T &val){\n        m_hasValue = true;\n        m_Value = val;\n    }\n    bool hasValue() const {\n        return m_hasValue;\n    }\n    T value() const{\n        return m_Value;\n    }\n};\n\nbool setDateTimeFormat(const QString &format);\nbool setDateTimeFormat(const Qt::DateFormat &format);\n\ntemplate <typename T>\nQString toStringValue(const QList<T> &val);\n\ntemplate <typename T>\nQString toStringValue(const QSet<T> &val);\n\ntemplate <typename T>\nbool fromStringValue(const QList<QString> &inStr, QList<T> &val);\n\ntemplate <typename T>\nbool fromStringValue(const QSet<QString> &inStr, QList<T> &val);\n\ntemplate <typename T>\nbool fromStringValue(const QMap<QString, QString> &inStr, QMap<QString, T> &val);\n\ntemplate <typename T>\nQJsonValue toJsonValue(const QList<T> &val);\n\ntemplate <typename T>\nQJsonValue toJsonValue(const QSet<T> &val);\n\ntemplate <typename T>\nQJsonValue toJsonValue(const QMap<QString, T> &val);\n\ntemplate <typename T>\nbool fromJsonValue(QList<T> &val, const QJsonValue &jval);\n\ntemplate <typename T>\nbool fromJsonValue(QSet<T> &val, const QJsonValue &jval);\n\ntemplate <typename T>\nbool fromJsonValue(QMap<QString, T> &val, const QJsonValue &jval);\n\nQString toStringValue(const QString &value);\nQString toStringValue(const QDateTime &value);\nQString toStringValue(const QByteArray &value);\nQString toStringValue(const QDate &value);\nQString toStringValue(const qint32 &value);\nQString toStringValue(const qint64 &value);\nQString toStringValue(const bool &value);\nQString toStringValue(const float &value);\nQString toStringValue(const double &value);\nQString toStringValue(const OAIObject &value);\nQString toStringValue(const OAIEnum &value);\nQString toStringValue(const OAIHttpFileElement &value);\n\ntemplate <typename T>\nQString toStringValue(const QList<T> &val) {\n    QString strArray;\n    for (const auto &item : val) {\n        strArray.append(toStringValue(item) + \",\");\n    }\n    if (val.count() > 0) {\n        strArray.chop(1);\n    }\n    return strArray;\n}\n\ntemplate <typename T>\nQString toStringValue(const QSet<T> &val) {\n    QString strArray;\n    for (const auto &item : val) {\n        strArray.append(toStringValue(item) + \",\");\n    }\n    if (val.count() > 0) {\n        strArray.chop(1);\n    }\n    return strArray;\n}\n\nQJsonValue toJsonValue(const QString &value);\nQJsonValue toJsonValue(const QDateTime &value);\nQJsonValue toJsonValue(const QByteArray &value);\nQJsonValue toJsonValue(const QDate &value);\nQJsonValue toJsonValue(const qint32 &value);\nQJsonValue toJsonValue(const qint64 &value);\nQJsonValue toJsonValue(const bool &value);\nQJsonValue toJsonValue(const float &value);\nQJsonValue toJsonValue(const double &value);\nQJsonValue toJsonValue(const OAIObject &value);\nQJsonValue toJsonValue(const OAIEnum &value);\nQJsonValue toJsonValue(const OAIHttpFileElement &value);\n\ntemplate <typename T>\nQJsonValue toJsonValue(const QList<T> &val) {\n    QJsonArray jArray;\n    for (const auto &item : val) {\n        jArray.append(toJsonValue(item));\n    }\n    return jArray;\n}\n\ntemplate <typename T>\nQJsonValue toJsonValue(const QSet<T> &val) {\n    QJsonArray jArray;\n    for (const auto &item : val) {\n        jArray.append(toJsonValue(item));\n    }\n    return jArray;\n}\n\ntemplate <typename T>\nQJsonValue toJsonValue(const QMap<QString, T> &val) {\n    QJsonObject jObject;\n    for (const auto &itemkey : val.keys()) {\n        jObject.insert(itemkey, toJsonValue(val.value(itemkey)));\n    }\n    return jObject;\n}\n\nbool fromStringValue(const QString &inStr, QString &value);\nbool fromStringValue(const QString &inStr, QDateTime &value);\nbool fromStringValue(const QString &inStr, QByteArray &value);\nbool fromStringValue(const QString &inStr, QDate &value);\nbool fromStringValue(const QString &inStr, qint32 &value);\nbool fromStringValue(const QString &inStr, qint64 &value);\nbool fromStringValue(const QString &inStr, bool &value);\nbool fromStringValue(const QString &inStr, float &value);\nbool fromStringValue(const QString &inStr, double &value);\nbool fromStringValue(const QString &inStr, OAIObject &value);\nbool fromStringValue(const QString &inStr, OAIEnum &value);\nbool fromStringValue(const QString &inStr, OAIHttpFileElement &value);\n\ntemplate <typename T>\nbool fromStringValue(const QList<QString> &inStr, QList<T> &val) {\n    bool ok = (inStr.count() > 0);\n    for (const auto &item : inStr) {\n        T itemVal;\n        ok &= fromStringValue(item, itemVal);\n        val.push_back(itemVal);\n    }\n    return ok;\n}\n\ntemplate <typename T>\nbool fromStringValue(const QSet<QString> &inStr, QList<T> &val) {\n    bool ok = (inStr.count() > 0);\n    for (const auto &item : inStr) {\n        T itemVal;\n        ok &= fromStringValue(item, itemVal);\n        val.push_back(itemVal);\n    }\n    return ok;\n}\n\ntemplate <typename T>\nbool fromStringValue(const QMap<QString, QString> &inStr, QMap<QString, T> &val) {\n    bool ok = (inStr.count() > 0);\n    for (const auto &itemkey : inStr.keys()) {\n        T itemVal;\n        ok &= fromStringValue(inStr.value(itemkey), itemVal);\n        val.insert(itemkey, itemVal);\n    }\n    return ok;\n}\n\nbool fromJsonValue(QString &value, const QJsonValue &jval);\nbool fromJsonValue(QDateTime &value, const QJsonValue &jval);\nbool fromJsonValue(QByteArray &value, const QJsonValue &jval);\nbool fromJsonValue(QDate &value, const QJsonValue &jval);\nbool fromJsonValue(qint32 &value, const QJsonValue &jval);\nbool fromJsonValue(qint64 &value, const QJsonValue &jval);\nbool fromJsonValue(bool &value, const QJsonValue &jval);\nbool fromJsonValue(float &value, const QJsonValue &jval);\nbool fromJsonValue(double &value, const QJsonValue &jval);\nbool fromJsonValue(OAIObject &value, const QJsonValue &jval);\nbool fromJsonValue(OAIEnum &value, const QJsonValue &jval);\nbool fromJsonValue(OAIHttpFileElement &value, const QJsonValue &jval);\n\ntemplate <typename T>\nbool fromJsonValue(QList<T> &val, const QJsonValue &jval) {\n    bool ok = true;\n    if (jval.isArray()) {\n        for (const auto jitem : jval.toArray()) {\n            T item;\n            ok &= fromJsonValue(item, jitem);\n            val.push_back(item);\n        }\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\ntemplate <typename T>\nbool fromJsonValue(QSet<T> &val, const QJsonValue &jval) {\n    bool ok = true;\n    if (jval.isArray()) {\n        for (const auto jitem : jval.toArray()) {\n            T item;\n            ok &= fromJsonValue(item, jitem);\n            val.insert(item);\n        }\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\ntemplate <typename T>\nbool fromJsonValue(QMap<QString, T> &val, const QJsonValue &jval) {\n    bool ok = true;\n    if (jval.isObject()) {\n        auto varmap = jval.toObject().toVariantMap();\n        if (varmap.count() > 0) {\n            for (const auto &itemkey : varmap.keys()) {\n                T itemVal;\n                ok &= fromJsonValue(itemVal, QJsonValue::fromVariant(varmap.value(itemkey)));\n                val.insert(itemkey, itemVal);\n            }\n        }\n    } else {\n        ok = false;\n    }\n    return ok;\n}\n\n} // namespace RespExtServer\n\n#endif // OAI_HELPERS_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIHttpFileElement.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include <QDebug>\n#include <QFile>\n#include <QJsonDocument>\n#include <QJsonObject>\n\n#include \"OAIHttpFileElement.h\"\n\nnamespace RespExtServer {\n\nvoid OAIHttpFileElement::setMimeType(const QString &mime) {\n    mime_type = mime;\n}\n\nvoid OAIHttpFileElement::setFileName(const QString &name) {\n    local_filename = name;\n}\n\nvoid OAIHttpFileElement::setVariableName(const QString &name) {\n    variable_name = name;\n}\n\nvoid OAIHttpFileElement::setRequestFileName(const QString &name) {\n    request_filename = name;\n}\n\nbool OAIHttpFileElement::isSet() const {\n    return !local_filename.isEmpty() || !request_filename.isEmpty();\n}\n\nQString OAIHttpFileElement::asJson() const {\n    QFile file(local_filename);\n    QByteArray bArray;\n    bool result = false;\n    if (file.exists()) {\n        result = file.open(QIODevice::ReadOnly);\n        bArray = file.readAll();\n        file.close();\n    }\n    if (!result) {\n        qDebug() << \"Error opening file \" << local_filename;\n    }\n    return QString(bArray);\n}\n\nQJsonValue OAIHttpFileElement::asJsonValue() const {\n    QFile file(local_filename);\n    QByteArray bArray;\n    bool result = false;\n    if (file.exists()) {\n        result = file.open(QIODevice::ReadOnly);\n        bArray = file.readAll();\n        file.close();\n    }\n    if (!result) {\n        qDebug() << \"Error opening file \" << local_filename;\n    }\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    return QJsonDocument::fromJson(bArray.data()).object();\n#else\n    return QJsonDocument::fromBinaryData(bArray.data()).object();\n#endif\n}\n\nbool OAIHttpFileElement::fromStringValue(const QString &instr) {\n    QFile file(local_filename);\n    bool result = false;\n    if (file.exists()) {\n        file.remove();\n    }\n    result = file.open(QIODevice::WriteOnly);\n    file.write(instr.toUtf8());\n    file.close();\n    if (!result) {\n        qDebug() << \"Error creating file \" << local_filename;\n    }\n    return result;\n}\n\nbool OAIHttpFileElement::fromJsonValue(const QJsonValue &jval) {\n    QFile file(local_filename);\n    bool result = false;\n    if (file.exists()) {\n        file.remove();\n    }\n    result = file.open(QIODevice::WriteOnly);\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    file.write(QJsonDocument(jval.toObject()).toJson());\n#else\n    file.write(QJsonDocument(jval.toObject()).toBinaryData());\n#endif\n    file.close();\n    if (!result) {\n        qDebug() << \"Error creating file \" << local_filename;\n    }\n    return result;\n}\n\nQByteArray OAIHttpFileElement::asByteArray() const {\n    QFile file(local_filename);\n    QByteArray bArray;\n    bool result = false;\n    if (file.exists()) {\n        result = file.open(QIODevice::ReadOnly);\n        bArray = file.readAll();\n        file.close();\n    }\n    if (!result) {\n        qDebug() << \"Error opening file \" << local_filename;\n    }\n    return bArray;\n}\n\nbool OAIHttpFileElement::fromByteArray(const QByteArray &bytes) {\n    QFile file(local_filename);\n    bool result = false;\n    if (file.exists()) {\n        file.remove();\n    }\n    result = file.open(QIODevice::WriteOnly);\n    file.write(bytes);\n    file.close();\n    if (!result) {\n        qDebug() << \"Error creating file \" << local_filename;\n    }\n    return result;\n}\n\nbool OAIHttpFileElement::saveToFile(const QString &varName, const QString &localFName, const QString &reqFname, const QString &mime, const QByteArray &bytes) {\n    setMimeType(mime);\n    setFileName(localFName);\n    setVariableName(varName);\n    setRequestFileName(reqFname);\n    return fromByteArray(bytes);\n}\n\nQByteArray OAIHttpFileElement::loadFromFile(const QString &varName, const QString &localFName, const QString &reqFname, const QString &mime) {\n    setMimeType(mime);\n    setFileName(localFName);\n    setVariableName(varName);\n    setRequestFileName(reqFname);\n    return asByteArray();\n}\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIHttpFileElement.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#ifndef OAI_HTTP_FILE_ELEMENT_H\n#define OAI_HTTP_FILE_ELEMENT_H\n\n#include <QJsonValue>\n#include <QMetaType>\n#include <QString>\n\nnamespace RespExtServer {\n\nclass OAIHttpFileElement {\n\npublic:\n    QString variable_name;\n    QString local_filename;\n    QString request_filename;\n    QString mime_type;\n    void setMimeType(const QString &mime);\n    void setFileName(const QString &name);\n    void setVariableName(const QString &name);\n    void setRequestFileName(const QString &name);\n    bool isSet() const;\n    bool fromStringValue(const QString &instr);\n    bool fromJsonValue(const QJsonValue &jval);\n    bool fromByteArray(const QByteArray &bytes);\n    bool saveToFile(const QString &variable_name, const QString &local_filename, const QString &request_filename, const QString &mime, const QByteArray &bytes);\n    QString asJson() const;\n    QJsonValue asJsonValue() const;\n    QByteArray asByteArray() const;\n    QByteArray loadFromFile(const QString &variable_name, const QString &local_filename, const QString &request_filename, const QString &mime);\n};\n\n} // namespace RespExtServer\n\nQ_DECLARE_METATYPE(RespExtServer::OAIHttpFileElement)\n\n#endif // OAI_HTTP_FILE_ELEMENT_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIHttpRequest.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include <QBuffer>\n#include <QDateTime>\n#include <QDir>\n#include <QFileInfo>\n#include <QTimer>\n#include <QUrl>\n#include <QUuid>\n#include <QtGlobal>\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    #define SKIP_EMPTY_PARTS Qt::SkipEmptyParts\n#else\n    #define SKIP_EMPTY_PARTS QString::SkipEmptyParts\n#endif\n\n#include \"OAIHttpRequest.h\"\n\nnamespace RespExtServer {\n\nOAIHttpRequestInput::OAIHttpRequestInput() {\n    initialize();\n}\n\nOAIHttpRequestInput::OAIHttpRequestInput(QString v_url_str, QString v_http_method) {\n    initialize();\n    url_str = v_url_str;\n    http_method = v_http_method;\n}\n\nvoid OAIHttpRequestInput::initialize() {\n    var_layout = NOT_SET;\n    url_str = \"\";\n    http_method = \"GET\";\n}\n\nvoid OAIHttpRequestInput::add_var(QString key, QString value) {\n    vars[key] = value;\n}\n\nvoid OAIHttpRequestInput::add_file(QString variable_name, QString local_filename, QString request_filename, QString mime_type) {\n    OAIHttpFileElement file;\n    file.variable_name = variable_name;\n    file.local_filename = local_filename;\n    file.request_filename = request_filename;\n    file.mime_type = mime_type;\n    files.append(file);\n}\n\nOAIHttpRequestWorker::OAIHttpRequestWorker(QObject *parent, QNetworkAccessManager *_manager)\n    : QObject(parent), manager(_manager), timeOutTimer(this), isResponseCompressionEnabled(false), isRequestCompressionEnabled(false), httpResponseCode(-1) {\n\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    randomGenerator = QRandomGenerator(QDateTime::currentDateTime().toSecsSinceEpoch());\n#else\n    qsrand(QDateTime::currentDateTime().toTime_t());\n#endif\n\n    if (manager == nullptr) {\n        manager = new QNetworkAccessManager(this);\n    }\n    workingDirectory = QDir::currentPath();\n    timeOutTimer.setSingleShot(true);\n}\n\nOAIHttpRequestWorker::~OAIHttpRequestWorker() {\n    QObject::disconnect(&timeOutTimer, &QTimer::timeout, nullptr, nullptr);\n    timeOutTimer.stop();\n    for (const auto &item : multiPartFields) {\n        if (item != nullptr) {\n            delete item;\n        }\n    }\n}\n\nQMap<QString, QString> OAIHttpRequestWorker::getResponseHeaders() const {\n    return headers;\n}\n\nOAIHttpFileElement OAIHttpRequestWorker::getHttpFileElement(const QString &fieldname) {\n    if (!files.isEmpty()) {\n        if (fieldname.isEmpty()) {\n            return files.first();\n        } else if (files.contains(fieldname)) {\n            return files[fieldname];\n        }\n    }\n    return OAIHttpFileElement();\n}\n\nQByteArray *OAIHttpRequestWorker::getMultiPartField(const QString &fieldname) {\n    if (!multiPartFields.isEmpty()) {\n        if (fieldname.isEmpty()) {\n            return multiPartFields.first();\n        } else if (multiPartFields.contains(fieldname)) {\n            return multiPartFields[fieldname];\n        }\n    }\n    return nullptr;\n}\n\nvoid OAIHttpRequestWorker::setTimeOut(int timeOutMs) {\n    timeOutTimer.setInterval(timeOutMs);\n    if(timeOutTimer.interval() == 0) {\n        QObject::disconnect(&timeOutTimer, &QTimer::timeout, nullptr, nullptr);\n    }\n}\n\nvoid OAIHttpRequestWorker::setWorkingDirectory(const QString &path) {\n    if (!path.isEmpty()) {\n        workingDirectory = path;\n    }\n}\n\nvoid OAIHttpRequestWorker::setResponseCompressionEnabled(bool enable) {\n    isResponseCompressionEnabled = enable;\n}\n\nvoid OAIHttpRequestWorker::setRequestCompressionEnabled(bool enable) {\n    isRequestCompressionEnabled = enable;\n}\n\nint  OAIHttpRequestWorker::getHttpResponseCode() const{\n    return httpResponseCode;\n}\n\nQString OAIHttpRequestWorker::http_attribute_encode(QString attribute_name, QString input) {\n    // result structure follows RFC 5987\n    bool need_utf_encoding = false;\n    QString result = \"\";\n    QByteArray input_c = input.toLocal8Bit();\n    char c;\n    for (int i = 0; i < input_c.length(); i++) {\n        c = input_c.at(i);\n        if (c == '\\\\' || c == '/' || c == '\\0' || c < ' ' || c > '~') {\n            // ignore and request utf-8 version\n            need_utf_encoding = true;\n        } else if (c == '\"') {\n            result += \"\\\\\\\"\";\n        } else {\n            result += c;\n        }\n    }\n\n    if (result.length() == 0) {\n        need_utf_encoding = true;\n    }\n\n    if (!need_utf_encoding) {\n        // return simple version\n        return QString(\"%1=\\\"%2\\\"\").arg(attribute_name, result);\n    }\n\n    QString result_utf8 = \"\";\n    for (int i = 0; i < input_c.length(); i++) {\n        c = input_c.at(i);\n        if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {\n            result_utf8 += c;\n        } else {\n            result_utf8 += \"%\" + QString::number(static_cast<unsigned char>(input_c.at(i)), 16).toUpper();\n        }\n    }\n\n    // return enhanced version with UTF-8 support\n    return QString(\"%1=\\\"%2\\\"; %1*=utf-8''%3\").arg(attribute_name, result, result_utf8);\n}\n\nvoid OAIHttpRequestWorker::execute(OAIHttpRequestInput *input) {\n\n    // reset variables\n    QNetworkReply *reply = nullptr;\n    QByteArray request_content = \"\";\n    response = \"\";\n    error_type = QNetworkReply::NoError;\n    error_str = \"\";\n    bool isFormData = false;\n\n    // decide on the variable layout\n\n    if (input->files.length() > 0) {\n        input->var_layout = MULTIPART;\n    }\n    if (input->var_layout == NOT_SET) {\n        input->var_layout = input->http_method == \"GET\" || input->http_method == \"HEAD\" ? ADDRESS : URL_ENCODED;\n    }\n\n    // prepare request content\n\n    QString boundary = \"\";\n\n    if (input->var_layout == ADDRESS || input->var_layout == URL_ENCODED) {\n        // variable layout is ADDRESS or URL_ENCODED\n\n        if (input->vars.count() > 0) {\n            bool first = true;\n            isFormData = true;\n            foreach (QString key, input->vars.keys()) {\n                if (!first) {\n                    request_content.append(\"&\");\n                }\n                first = false;\n\n                request_content.append(QUrl::toPercentEncoding(key));\n                request_content.append(\"=\");\n                request_content.append(QUrl::toPercentEncoding(input->vars.value(key)));\n            }\n\n            if (input->var_layout == ADDRESS) {\n                input->url_str += \"?\" + request_content;\n                request_content = \"\";\n            }\n        }\n    } else {\n        // variable layout is MULTIPART\n\n        boundary = QString(\"__-----------------------%1%2\")\n                    #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n                            .arg(QDateTime::currentDateTime().toSecsSinceEpoch())\n                            .arg(randomGenerator.generate());\n                    #else\n                            .arg(QDateTime::currentDateTime().toTime_t())\n                            .arg(qrand());\n                    #endif\n        QString boundary_delimiter = \"--\";\n        QString new_line = \"\\r\\n\";\n\n        // add variables\n        foreach (QString key, input->vars.keys()) {\n            // add boundary\n            request_content.append(boundary_delimiter.toUtf8());\n            request_content.append(boundary.toUtf8());\n            request_content.append(new_line.toUtf8());\n\n            // add header\n            request_content.append(\"Content-Disposition: form-data; \");\n            request_content.append(http_attribute_encode(\"name\", key).toUtf8());\n            request_content.append(new_line.toUtf8());\n            request_content.append(\"Content-Type: text/plain\");\n            request_content.append(new_line.toUtf8());\n\n            // add header to body splitter\n            request_content.append(new_line.toUtf8());\n\n            // add variable content\n            request_content.append(input->vars.value(key).toUtf8());\n            request_content.append(new_line.toUtf8());\n        }\n\n        // add files\n        for (QList<OAIHttpFileElement>::iterator file_info = input->files.begin(); file_info != input->files.end(); file_info++) {\n            QFileInfo fi(file_info->local_filename);\n\n            // ensure necessary variables are available\n            if (file_info->local_filename == nullptr\n                || file_info->local_filename.isEmpty()\n                || file_info->variable_name == nullptr\n                || file_info->variable_name.isEmpty()\n                || !fi.exists()\n                || !fi.isFile()\n                || !fi.isReadable()) {\n                // silent abort for the current file\n                continue;\n            }\n\n            QFile file(file_info->local_filename);\n            if (!file.open(QIODevice::ReadOnly)) {\n                // silent abort for the current file\n                continue;\n            }\n\n            // ensure filename for the request\n            if (file_info->request_filename == nullptr || file_info->request_filename.isEmpty()) {\n                file_info->request_filename = fi.fileName();\n                if (file_info->request_filename.isEmpty()) {\n                    file_info->request_filename = \"file\";\n                }\n            }\n\n            // add boundary\n            request_content.append(boundary_delimiter.toUtf8());\n            request_content.append(boundary.toUtf8());\n            request_content.append(new_line.toUtf8());\n\n            // add header\n            request_content.append(\n                QString(\"Content-Disposition: form-data; %1; %2\").arg(http_attribute_encode(\"name\", file_info->variable_name), http_attribute_encode(\"filename\", file_info->request_filename)).toUtf8());\n            request_content.append(new_line.toUtf8());\n\n            if (file_info->mime_type != nullptr && !file_info->mime_type.isEmpty()) {\n                request_content.append(\"Content-Type: \");\n                request_content.append(file_info->mime_type.toUtf8());\n                request_content.append(new_line.toUtf8());\n            }\n\n            request_content.append(\"Content-Transfer-Encoding: binary\");\n            request_content.append(new_line.toUtf8());\n\n            // add header to body splitter\n            request_content.append(new_line.toUtf8());\n\n            // add file content\n            request_content.append(file.readAll());\n            request_content.append(new_line.toUtf8());\n\n            file.close();\n        }\n\n        // add end of body\n        request_content.append(boundary_delimiter.toUtf8());\n        request_content.append(boundary.toUtf8());\n        request_content.append(boundary_delimiter.toUtf8());\n    }\n\n    if (input->request_body.size() > 0) {\n        qDebug() << \"got a request body\";\n        request_content.clear();\n        if(!isFormData && (input->var_layout != MULTIPART) && isRequestCompressionEnabled){\n            request_content.append(compress(input->request_body, 7, OAICompressionType::Gzip));\n        } else {\n            request_content.append(input->request_body);\n        }\n    }\n    // prepare connection\n\n    QNetworkRequest request = QNetworkRequest(QUrl(input->url_str));\n    if (OAIHttpRequestWorker::sslDefaultConfiguration != nullptr) {\n        request.setSslConfiguration(*OAIHttpRequestWorker::sslDefaultConfiguration);\n    }\n    request.setRawHeader(\"User-Agent\", \"OpenAPI-Generator/1.0.0/cpp-qt\");\n    foreach (QString key, input->headers.keys()) { request.setRawHeader(key.toStdString().c_str(), input->headers.value(key).toStdString().c_str()); }\n\n    if (request_content.size() > 0 && !isFormData && (input->var_layout != MULTIPART)) {\n        if (!input->headers.contains(\"Content-Type\")) {\n            request.setHeader(QNetworkRequest::ContentTypeHeader, \"application/json\");\n        } else {\n            request.setHeader(QNetworkRequest::ContentTypeHeader, input->headers.value(\"Content-Type\"));\n        }\n        if(isRequestCompressionEnabled){\n            request.setRawHeader(\"Content-Encoding\", \"gzip\");\n        }\n    } else if (input->var_layout == URL_ENCODED) {\n        request.setHeader(QNetworkRequest::ContentTypeHeader, \"application/x-www-form-urlencoded\");\n    } else if (input->var_layout == MULTIPART) {\n        request.setHeader(QNetworkRequest::ContentTypeHeader, \"multipart/form-data; boundary=\" + boundary);\n    }\n\n    if(isResponseCompressionEnabled){\n        request.setRawHeader(\"Accept-Encoding\", \"gzip\");\n    } else {\n        request.setRawHeader(\"Accept-Encoding\", \"identity\");\n    }\n\n    if (input->http_method == \"GET\") {\n        reply = manager->get(request);\n    } else if (input->http_method == \"POST\") {\n        reply = manager->post(request, request_content);\n    } else if (input->http_method == \"PUT\") {\n        reply = manager->put(request, request_content);\n    } else if (input->http_method == \"HEAD\") {\n        reply = manager->head(request);\n    } else if (input->http_method == \"DELETE\") {\n        reply = manager->deleteResource(request);\n    } else {\n#if (QT_VERSION >= 0x050800)\n        reply = manager->sendCustomRequest(request, input->http_method.toLatin1(), request_content);\n#else\n        QBuffer *buffer = new QBuffer;\n        buffer->setData(request_content);\n        buffer->open(QIODevice::ReadOnly);\n\n        reply = manager->sendCustomRequest(request, input->http_method.toLatin1(), buffer);\n        buffer->setParent(reply);\n#endif\n    }\n    if (reply != nullptr) {\n        reply->setParent(this);\n        connect(reply, &QNetworkReply::finished, [this, reply] {\n            on_reply_finished(reply);\n        });\n    }\n    if (timeOutTimer.interval() > 0) {\n        QObject::connect(&timeOutTimer, &QTimer::timeout, [this, reply] {\n            on_reply_timeout(reply);\n        });\n        timeOutTimer.start();\n    }\n}\n\nvoid OAIHttpRequestWorker::on_reply_finished(QNetworkReply *reply) {\n    bool codeSts = false;\n    if(timeOutTimer.isActive()) {\n        QObject::disconnect(&timeOutTimer, &QTimer::timeout, nullptr, nullptr);\n        timeOutTimer.stop();\n    }\n    error_type = reply->error();\n    error_str = reply->errorString();\n    if (reply->rawHeaderPairs().count() > 0) {\n        for (const auto &item : reply->rawHeaderPairs()) {\n            headers.insert(item.first, item.second);\n        }\n    }\n    auto rescode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&codeSts);\n    if(codeSts){\n        httpResponseCode = rescode;\n    } else{\n        httpResponseCode = -1;\n    }\n    process_response(reply);\n    reply->deleteLater();\n    emit on_execution_finished(this);\n}\n\nvoid OAIHttpRequestWorker::on_reply_timeout(QNetworkReply *reply) {\n    error_type = QNetworkReply::TimeoutError;\n    response = \"\";\n    error_str = \"Timed out waiting for response\";\n    disconnect(reply, nullptr, nullptr, nullptr);\n    reply->abort();\n    reply->deleteLater();\n    emit on_execution_finished(this);\n}\n\nvoid OAIHttpRequestWorker::process_response(QNetworkReply *reply) {\n    QString contentDispositionHdr;\n    QString contentTypeHdr;\n    QString contentEncodingHdr;\n\n    for(auto hdr: getResponseHeaders().keys()){\n        if(hdr.compare(QString(\"Content-Disposition\"), Qt::CaseInsensitive) == 0){\n            contentDispositionHdr = getResponseHeaders().value(hdr);\n        }\n        if(hdr.compare(QString(\"Content-Type\"), Qt::CaseInsensitive) == 0){\n            contentTypeHdr = getResponseHeaders().value(hdr);\n        }\n        if(hdr.compare(QString(\"Content-Encoding\"), Qt::CaseInsensitive) == 0){\n            contentEncodingHdr = getResponseHeaders().value(hdr);\n        }\n    }\n\n    if (!contentDispositionHdr.isEmpty()) {\n        auto contentDisposition = contentDispositionHdr.split(QString(\";\"), SKIP_EMPTY_PARTS);\n        auto contentType =\n            !contentTypeHdr.isEmpty() ? contentTypeHdr.split(QString(\";\"), SKIP_EMPTY_PARTS).first() : QString();\n        if ((contentDisposition.count() > 0) && (contentDisposition.first() == QString(\"attachment\"))) {\n            QString filename = QUuid::createUuid().toString();\n            for (const auto &file : contentDisposition) {\n                if (file.contains(QString(\"filename\"))) {\n                    filename = file.split(QString(\"=\"), SKIP_EMPTY_PARTS).at(1);\n                    break;\n                }\n            }\n            OAIHttpFileElement felement;\n            felement.saveToFile(QString(), workingDirectory + QDir::separator() + filename, filename, contentType, reply->readAll());\n            files.insert(filename, felement);\n        }\n\n    } else if (!contentTypeHdr.isEmpty()) {\n        auto contentType = contentTypeHdr.split(QString(\";\"), SKIP_EMPTY_PARTS);\n        if ((contentType.count() > 0) && (contentType.first() == QString(\"multipart/form-data\"))) {\n            // TODO : Handle Multipart responses\n        } else {\n            if(!contentEncodingHdr.isEmpty()){\n                auto encoding = contentEncodingHdr.split(QString(\";\"), SKIP_EMPTY_PARTS);\n                if(encoding.count() > 0){\n                    auto compressionTypes = encoding.first().split(',', SKIP_EMPTY_PARTS);\n                    if(compressionTypes.contains(\"gzip\", Qt::CaseInsensitive) || compressionTypes.contains(\"deflate\", Qt::CaseInsensitive)){\n                        response = decompress(reply->readAll());\n                    } else if(compressionTypes.contains(\"identity\", Qt::CaseInsensitive)){\n                        response = reply->readAll();\n                    }\n                }\n            }\n            else {\n                response = reply->readAll();\n            }\n        }\n    }\n}\n\nQByteArray OAIHttpRequestWorker::decompress(const QByteArray& data){\n    \n    Q_UNUSED(data);\n    return QByteArray();\n}\n\nQByteArray OAIHttpRequestWorker::compress(const QByteArray& input, int level, OAICompressionType compressType) {\n    \n    Q_UNUSED(input);\n    Q_UNUSED(level);\n    Q_UNUSED(compressType);\n    return QByteArray();\n}\n\nQSslConfiguration *OAIHttpRequestWorker::sslDefaultConfiguration;\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIHttpRequest.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/**\n * Based on http://www.creativepulse.gr/en/blog/2014/restful-api-requests-using-qt-cpp-for-linux-mac-osx-ms-windows\n * By Alex Stylianos\n *\n **/\n\n#ifndef OAI_HTTPREQUESTWORKER_H\n#define OAI_HTTPREQUESTWORKER_H\n\n#include <QMap>\n#include <QNetworkAccessManager>\n#include <QNetworkReply>\n#include <QObject>\n#include <QString>\n#include <QTimer>\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    #include <QRandomGenerator>\n#endif\n\n#include \"OAIHttpFileElement.h\"\n\nnamespace RespExtServer {\n\nenum OAIHttpRequestVarLayout {\n    NOT_SET,\n    ADDRESS,\n    URL_ENCODED,\n    MULTIPART\n};\n\nclass OAIHttpRequestInput {\n\npublic:\n    QString url_str;\n    QString http_method;\n    OAIHttpRequestVarLayout var_layout;\n    QMap<QString, QString> vars;\n    QMap<QString, QString> headers;\n    QList<OAIHttpFileElement> files;\n    QByteArray request_body;\n\n    OAIHttpRequestInput();\n    OAIHttpRequestInput(QString v_url_str, QString v_http_method);\n    void initialize();\n    void add_var(QString key, QString value);\n    void add_file(QString variable_name, QString local_filename, QString request_filename, QString mime_type);\n};\n\nclass OAIHttpRequestWorker : public QObject {\n    Q_OBJECT\n\npublic:\n    explicit OAIHttpRequestWorker(QObject *parent = nullptr, QNetworkAccessManager *manager = nullptr);\n    virtual ~OAIHttpRequestWorker();\n\n    QByteArray response;\n    QNetworkReply::NetworkError error_type;\n    QString error_str;\n\n    QMap<QString, QString> getResponseHeaders() const;\n    QString http_attribute_encode(QString attribute_name, QString input);\n    void execute(OAIHttpRequestInput *input);\n    static QSslConfiguration *sslDefaultConfiguration;\n    void setTimeOut(int timeOutMs);\n    void setWorkingDirectory(const QString &path);\n    OAIHttpFileElement getHttpFileElement(const QString &fieldname = QString());\n    QByteArray *getMultiPartField(const QString &fieldname = QString());\n    void setResponseCompressionEnabled(bool enable);\n    void setRequestCompressionEnabled(bool enable);\n    int  getHttpResponseCode() const;\n\nsignals:\n    void on_execution_finished(OAIHttpRequestWorker *worker);\n\nprivate:\n    enum OAICompressionType{\n        Zlib,\n        Gzip\n    };\n    QNetworkAccessManager *manager;\n    QMap<QString, QString> headers;\n    QMap<QString, OAIHttpFileElement> files;\n    QMap<QString, QByteArray *> multiPartFields;\n    QString workingDirectory;\n    QTimer timeOutTimer;\n    bool isResponseCompressionEnabled;\n    bool isRequestCompressionEnabled;\n    int  httpResponseCode;\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    QRandomGenerator randomGenerator;\n#endif\n\n    void on_reply_timeout(QNetworkReply *reply);\n    void on_reply_finished(QNetworkReply *reply);\n    void process_response(QNetworkReply *reply);\n    QByteArray decompress(const QByteArray& data);\n    QByteArray compress(const QByteArray& input, int level, OAICompressionType compressType);\n};\n\n} // namespace RespExtServer\n\n#endif // OAI_HTTPREQUESTWORKER_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIInline_response_400.cpp",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#include \"OAIInline_response_400.h\"\n\n#include <QDebug>\n#include <QJsonArray>\n#include <QJsonDocument>\n#include <QObject>\n\n#include \"OAIHelpers.h\"\n\nnamespace RespExtServer {\n\nOAIInline_response_400::OAIInline_response_400(QString json) {\n    this->initializeModel();\n    this->fromJson(json);\n}\n\nOAIInline_response_400::OAIInline_response_400() {\n    this->initializeModel();\n}\n\nOAIInline_response_400::~OAIInline_response_400() {}\n\nvoid OAIInline_response_400::initializeModel() {\n\n    m_error_isSet = false;\n    m_error_isValid = false;\n}\n\nvoid OAIInline_response_400::fromJson(QString jsonString) {\n    QByteArray array(jsonString.toStdString().c_str());\n    QJsonDocument doc = QJsonDocument::fromJson(array);\n    QJsonObject jsonObject = doc.object();\n    this->fromJsonObject(jsonObject);\n}\n\nvoid OAIInline_response_400::fromJsonObject(QJsonObject json) {\n\n    m_error_isValid = ::RespExtServer::fromJsonValue(error, json[QString(\"error\")]);\n    m_error_isSet = !json[QString(\"error\")].isNull() && m_error_isValid;\n}\n\nQString OAIInline_response_400::asJson() const {\n    QJsonObject obj = this->asJsonObject();\n    QJsonDocument doc(obj);\n    QByteArray bytes = doc.toJson();\n    return QString(bytes);\n}\n\nQJsonObject OAIInline_response_400::asJsonObject() const {\n    QJsonObject obj;\n    if (m_error_isSet) {\n        obj.insert(QString(\"error\"), ::RespExtServer::toJsonValue(error));\n    }\n    return obj;\n}\n\nQString OAIInline_response_400::getError() const {\n    return error;\n}\nvoid OAIInline_response_400::setError(const QString &error) {\n    this->error = error;\n    this->m_error_isSet = true;\n}\n\nbool OAIInline_response_400::is_error_Set() const{\n    return m_error_isSet;\n}\n\nbool OAIInline_response_400::is_error_Valid() const{\n    return m_error_isValid;\n}\n\nbool OAIInline_response_400::isSet() const {\n    bool isObjectUpdated = false;\n    do {\n        if (m_error_isSet) {\n            isObjectUpdated = true;\n            break;\n        }\n    } while (false);\n    return isObjectUpdated;\n}\n\nbool OAIInline_response_400::isValid() const {\n    // only required properties are required for the object to be considered valid\n    return true;\n}\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIInline_response_400.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/*\n * OAIInline_response_400.h\n *\n * \n */\n\n#ifndef OAIInline_response_400_H\n#define OAIInline_response_400_H\n\n#include <QJsonObject>\n\n#include <QString>\n\n#include \"OAIEnum.h\"\n#include \"OAIObject.h\"\n\nnamespace RespExtServer {\n\nclass OAIInline_response_400 : public OAIObject {\npublic:\n    OAIInline_response_400();\n    OAIInline_response_400(QString json);\n    ~OAIInline_response_400() override;\n\n    QString asJson() const override;\n    QJsonObject asJsonObject() const override;\n    void fromJsonObject(QJsonObject json) override;\n    void fromJson(QString jsonString) override;\n\n    QString getError() const;\n    void setError(const QString &error);\n    bool is_error_Set() const;\n    bool is_error_Valid() const;\n\n    virtual bool isSet() const override;\n    virtual bool isValid() const override;\n\nprivate:\n    void initializeModel();\n\n    QString error;\n    bool m_error_isSet;\n    bool m_error_isValid;\n};\n\n} // namespace RespExtServer\n\nQ_DECLARE_METATYPE(RespExtServer::OAIInline_response_400)\n\n#endif // OAIInline_response_400_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIOauth.cpp",
    "content": "#include \"OAIOauth.h\"\n\nnamespace RespExtServer {\n\n/*\n * Base class to perform oauth2 flows\n *\n */\n\n\nvoid OauthBase::onFinish(QNetworkReply *rep)\n{\n    //TODO emit error signal when token is wrong\n    QJsonDocument document = QJsonDocument::fromJson(rep->readAll());\n    QJsonObject rootObj = document.object();\n    QString token = rootObj.find(\"access_token\").value().toString();\n    QString scope = rootObj.find(\"scope\").value().toString();\n    QString type = rootObj.find(\"token_type\").value().toString();\n    int expiresIn = rootObj.find(\"expires_in\").value().toInt();\n    addToken(oauthToken(token, expiresIn, scope, type));\n}\n\noauthToken OauthBase::getToken(QString scope)\n{\n    auto tokenIt = m_oauthTokenMap.find(scope);\n    return tokenIt != m_oauthTokenMap.end() ? tokenIt.value() : oauthToken();\n}\n\nvoid OauthBase::addToken(oauthToken token)\n{\n    m_oauthTokenMap.insert(token.getScope(),token);\n    emit tokenReceived();\n\n}\n\nvoid OauthBase::removeToken(QString scope)\n{\n    m_oauthTokenMap.remove(scope);\n}\n\n/*\n * Class to perform the authorization code flow\n *\n */\n\nOauthCode::OauthCode(QObject *parent) : OauthBase(parent){}\n\nvoid OauthCode::link(){\n    connect(&m_server, SIGNAL(dataReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));\n    connect(this, SIGNAL(authenticationNeeded()), this, SLOT(authenticationNeededCallback()));\n    connect(this, SIGNAL(tokenReceived()), &m_server, SLOT(stop()));\n}\n\nvoid OauthCode::unlink()\n{\n    disconnect(this,0,0,0);\n    disconnect(&m_server,0,0,0);\n}\n\nvoid OauthCode::setVariables(QString authUrl, QString tokenUrl, QString scope, QString state, QString redirectUri, QString clientId, QString clientSecret, QString accessType){\n\n    m_authUrl = QUrl(authUrl);\n    m_tokenUrl = QUrl(tokenUrl);\n    m_scope = scope;\n    m_accessType = accessType;\n    m_state = state;\n    m_redirectUri = redirectUri;\n    m_clientId = clientId;\n    m_clientSecret = clientSecret;\n\n}\n\nvoid OauthCode::authenticationNeededCallback()\n{\n    QDesktopServices::openUrl(QUrl(m_authUrl.toString() + \"?scope=\" + m_scope + (m_accessType==\"\" ? \"\" : \"&access_type=\" + m_accessType) + \"&response_type=code\" + \"&state=\" + m_state + \"&redirect_uri=\" + m_redirectUri + \"&client_id=\" + m_clientId));\n    m_server.start();\n}\n\nvoid OauthCode::onVerificationReceived(const QMap<QString, QString> response) {\n\n        // Save access code\n        QString state(response.value(\"state\"));\n        QString scope(response.value(\"scope\"));\n        QString code(response.value(\"code\"));\n\n        //create query with the required fields\n        QUrlQuery postData;\n        postData.addQueryItem(\"grant_type\", \"authorization_code\");\n        postData.addQueryItem(\"client_id\", m_clientId);\n        postData.addQueryItem(\"client_secret\", m_clientSecret);\n        postData.addQueryItem(\"code\", code);\n        postData.addQueryItem(\"redirect_uri\", m_redirectUri);\n        QNetworkAccessManager * manager = new QNetworkAccessManager(this);\n\n        QNetworkRequest request(m_tokenUrl);\n\n        request.setHeader(QNetworkRequest::ContentTypeHeader, \"application/x-www-form-urlencoded\");\n\n        connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onFinish(QNetworkReply *)));\n\n        manager->post(request, postData.query().toUtf8());\n}\n\n/*\n * Class to perform the implicit flow\n *\n */\n\nOauthImplicit::OauthImplicit(QObject *parent) : OauthBase(parent){}\n\nvoid OauthImplicit::link()\n{\n    //TODO correct linking\n    connect(&m_server, SIGNAL(dataReceived(QMap<QString,QString>)), this, SLOT(ImplicitTokenReceived(QMap<QString,QString>)));\n    connect(this, SIGNAL(authenticationNeeded()), this, SLOT(authenticationNeededCallback()));\n    connect(this, SIGNAL(tokenReceived()), &m_server, SLOT(stop()));\n    m_linked = true;\n}\n\nvoid OauthImplicit::unlink()\n{\n     disconnect(this,0,0,0);\n     disconnect(&m_server,0,0,0);\n     m_linked = false;\n}\n\nvoid OauthImplicit::setVariables(QString authUrl, QString scope, QString state, QString redirectUri, QString clientId, QString accessType){\n\n    m_authUrl = QUrl(authUrl);\n    m_scope = scope;\n    m_accessType = accessType;\n    m_state = state;\n    m_redirectUri = redirectUri;\n    m_clientId = clientId;\n\n}\n\nvoid OauthImplicit::authenticationNeededCallback()\n{\n     QDesktopServices::openUrl(QUrl(m_authUrl.toString() + \"?scope=\" + m_scope + (m_accessType==\"\" ? \"\" : \"&access_type=\" + m_accessType) + \"&response_type=token\" + \"&state=\" + m_state + \"&redirect_uri=\" + m_redirectUri + \"&client_id=\" + m_clientId));\n     m_server.start();\n}\n\nvoid OauthImplicit::ImplicitTokenReceived(const QMap<QString, QString> response)\n{\n    QString token = response.find(\"access_token\").value();\n    QString scope = response.find(\"scope\").value();\n    QString type = response.find(\"token_type\").value();\n    int expiresIn = response.find(\"expires_in\").value().toInt();\n    addToken(oauthToken(token, expiresIn, scope, type));\n}\n\n\n/*\n * Class to perform the client credentials flow\n *\n */\nOauthCredentials::OauthCredentials(QObject *parent) : OauthBase(parent){}\nvoid OauthCredentials::link()\n{\n    connect(this, SIGNAL(authenticationNeeded()), this, SLOT(authenticationNeededCallback()));\n}\n\nvoid OauthCredentials::unlink()\n{\n    disconnect(this,0,0,0);\n}\n\nvoid OauthCredentials::setVariables(QString tokenUrl, QString scope, QString clientId, QString clientSecret){\n\n    m_tokenUrl = QUrl(tokenUrl);\n    m_scope = scope;\n    m_clientId = clientId;\n    m_clientSecret = clientSecret;\n\n}\n\nvoid OauthCredentials::authenticationNeededCallback()\n{\n    //create query with the required fields\n    QUrlQuery postData;\n    postData.addQueryItem(\"grant_type\", \"client_credentials\");\n    postData.addQueryItem(\"client_id\", m_clientId);\n    postData.addQueryItem(\"client_secret\", m_clientSecret);\n    postData.addQueryItem(\"scope\", m_scope);\n    QNetworkAccessManager * manager = new QNetworkAccessManager(this);\n\n    QNetworkRequest request(m_tokenUrl);\n\n    request.setHeader(QNetworkRequest::ContentTypeHeader, \"application/x-www-form-urlencoded\");\n\n    connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onFinish(QNetworkReply *)));\n\n    manager->post(request, postData.query().toUtf8());\n}\n\n/*\n * Class to perform the resource owner password flow\n *\n */\nOauthPassword::OauthPassword(QObject *parent) : OauthBase(parent){}\nvoid OauthPassword::link()\n{\n    connect(this, SIGNAL(authenticationNeeded()), this, SLOT(authenticationNeededCallback()));\n}\n\nvoid OauthPassword::unlink()\n{\n    disconnect(this,0,0,0);\n}\n\nvoid OauthPassword::setVariables(QString tokenUrl, QString scope, QString clientId, QString clientSecret, QString username, QString password){\n\n    m_tokenUrl = QUrl(tokenUrl);\n    m_scope = scope;\n    m_clientId = clientId;\n    m_clientSecret = clientSecret;\n    m_username = username;\n    m_password = password;\n\n}\nvoid OauthPassword::authenticationNeededCallback()\n{\n    //create query with the required fields\n    QUrlQuery postData;\n    postData.addQueryItem(\"grant_type\", \"password\");\n    postData.addQueryItem(\"username\", m_username);\n    postData.addQueryItem(\"password\", m_password);\n    postData.addQueryItem(\"client_id\", m_clientId);\n    postData.addQueryItem(\"client_secret\", m_clientSecret);\n    postData.addQueryItem(\"scope\", m_scope);\n    QNetworkAccessManager * manager = new QNetworkAccessManager(this);\n\n    QNetworkRequest request(m_tokenUrl);\n\n    request.setHeader(QNetworkRequest::ContentTypeHeader, \"application/x-www-form-urlencoded\");\n\n    connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onFinish(QNetworkReply *)));\n\n    manager->post(request, postData.query().toUtf8());\n}\n\n\n/*\n * Class that provides a simple reply server\n *\n */\n\nReplyServer::ReplyServer(QObject *parent) : QTcpServer(parent)\n{\n      connect(this, SIGNAL(newConnection()), this, SLOT(onConnected()));\n      m_reply =\"you can close this window now!\";\n}\n\nvoid ReplyServer::start()\n{\n    if(!listen(QHostAddress::Any, 9999))\n    {\n        qDebug() << \"Server could not start\";\n    }\n    else\n    {\n        qDebug() << \"Server started!\";\n    }\n}\n\nvoid ReplyServer::stop()\n{\n    qDebug() << \"Stopping the Server...\";\n    QTcpServer::close();\n}\n\nvoid ReplyServer::onConnected()\n{\n    // need to grab the socket\n    QTcpSocket *socket = nextPendingConnection();\n    connect(socket, SIGNAL(readyRead()), this, SLOT(read()), Qt::UniqueConnection);\n    connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));\n\n}\n\nvoid ReplyServer::read()\n{\n    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());\n    if (!socket) {\n        qDebug() << \"No socket available\";\n        return;\n    }\n    qDebug() << \"Socket connected\";\n\n    QTextStream os(socket);\n                     os.setAutoDetectUnicode(true);\n                     os << \"HTTP/1.0 200 Ok\\r\\n\"\n                         \"Content-Type: text/html; charset=\\\"utf-8\\\"\\r\\n\"\n                         \"\\r\\n\"\n                        <<\"<!DOCTYPE html>\\\n                        <html>\\\n                        <head>\\\n                        <script>\\\n                        window.onload = function hashFunction() {\\\n                             var query = location.hash.substr(1);\\\n                             if (query != \\\"\\\") {\\\n                                 var xhttp = new XMLHttpRequest();\\\n                                 xhttp.open(\\\"GET\\\", \\\"/?\\\" + query, true);\\\n                                 xhttp.send();\\\n                             }\\\n                        }\\\n                        </script>\\\n                        </head>\\\n                        <body>\\\n                        <h2>You can close this window now!</h2>\\\n                        </body>\\\n                        </html>\";\n\n    QByteArray data = socket->readLine();\n    QString splitGetLine = QString(data);\n    splitGetLine.remove(\"GET\");\n    splitGetLine.remove(\"HTTP/1.1\");\n    splitGetLine.remove(\"\\r\\n\");\n    splitGetLine.remove(\" \");\n    //prefix is needed to extract query params\n    QUrl getTokenUrl(\"http://\" + splitGetLine);\n    QList< QPair<QString, QString> > tokens;\n\n    QUrlQuery query(getTokenUrl);\n    tokens = query.queryItems();\n\n\n    QMap<QString, QString> queryParams;\n    QPair<QString, QString> tokenPair;\n    foreach (tokenPair, tokens) {\n        QString key = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.first.trimmed().toLatin1()));\n        QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed().toLatin1()));\n        queryParams.insert(key, value);\n    }\n    if (!queryParams.contains(\"state\")) {\n\n        socket->close();\n        return;\n    }\n    socket->close();\n\n    emit dataReceived(queryParams);\n}\n\n} // namespace RespExtServer\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIOauth.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/**\n * Providing a Oauth2 Class and a ReplyServer for the Oauth2 authorization code flow. \n */\n#ifndef OAI_OAUTH2_H\n#define OAI_OAUTH2_H\n\n#include <QObject>\n#include <QtCore>\n#include <QtNetwork>\n#include <QDesktopServices>\n#include <QNetworkRequest>\n#include <QNetworkReply>\n#include <QNetworkAccessManager>\n#include <QtDebug>\n#include <QTcpServer>\n#include <QTcpSocket>\n#include <QByteArray>\n#include <QString>\n#include <QMap>\n#include <QUrl>\n#include <QUrlQuery>\n#include <QDateTime>\n#include <time.h>\n\nnamespace RespExtServer {\n\nclass oauthToken\n{\npublic:\n    oauthToken(QString token, int expiresIn, QString scope, QString tokenType) : m_token(token), m_scope(scope), m_type(tokenType){\n        m_validUntil = time(0) + expiresIn;\n    }\n    oauthToken(){\n        m_validUntil = time(0) - 1;\n    }\n    QString getToken(){return m_token;};\n    QString getScope(){return m_scope;};\n    QString getType(){return m_type;};\n    bool isValid(){return time(0) < m_validUntil;};\n\nprivate:\n    QString m_token;\n    time_t m_validUntil;\n    QString m_scope;\n    QString m_type;\n};\n\nclass ReplyServer : public QTcpServer\n{\n    Q_OBJECT\npublic:\n    explicit ReplyServer(QObject *parent = nullptr);\n    void setReply(QByteArray reply){m_reply = reply;};\n    void run();\nprivate:\n    QByteArray m_reply;\n\nsignals:\n    void dataReceived(QMap<QString, QString>);\n\npublic slots:\n    void onConnected();\n    void read();\n    void start();\n    void stop();\n};\n\n//Base class\nclass OauthBase : public QObject\n{\n    Q_OBJECT\npublic: \n    OauthBase(QObject* parent = nullptr) : QObject(parent) {};\n    oauthToken getToken(QString scope);\n    void addToken(oauthToken token);\n    void removeToken(QString scope);\n    bool linked(){return m_linked;};\n    virtual void link()=0;\n    virtual void unlink()=0;\n\nprotected:\n    QMap<QString, oauthToken> m_oauthTokenMap;\n    QUrl m_authUrl;\n    QUrl m_tokenUrl;\n    QString m_scope, m_accessType, m_state, m_redirectUri, m_clientId, m_clientSecret;\n    bool m_linked;\n\npublic slots:\n    virtual void authenticationNeededCallback()=0;\n    void onFinish(QNetworkReply *rep);\n\nsignals:\n    void authenticationNeeded();\n    void tokenReceived();\n};\n\n// Authorization code flow\nclass OauthCode : public OauthBase\n{\n    Q_OBJECT\npublic:\n    OauthCode(QObject *parent = nullptr);\n    void link() override;\n    void unlink() override;\n    void setVariables(QString authUrl, QString tokenUrl, QString scope, QString state, QString redirectUri, QString clientId, QString clientSecret, QString accessType = \"\");\n\nprivate:\n    ReplyServer m_server;\n\npublic slots:\n    void authenticationNeededCallback() override;\n    void onVerificationReceived(const QMap<QString, QString> response);\n\n};\n\n// Implicit flow\nclass OauthImplicit : public OauthBase\n{\n    Q_OBJECT\npublic:\n    OauthImplicit(QObject *parent = nullptr);\n    void link() override;\n    void unlink() override;\n    void setVariables(QString authUrl, QString scope, QString state, QString redirectUri, QString clientId, QString accessType = \"\");\n\nprivate:\n    ReplyServer m_server;\n\npublic slots:\n    void authenticationNeededCallback() override;\n    void ImplicitTokenReceived(const QMap<QString, QString> response);\n};\n\n//client credentials flow\nclass OauthCredentials : public OauthBase\n{\n    Q_OBJECT\npublic:\n    OauthCredentials(QObject *parent = nullptr);\n    void link() override;\n    void unlink() override;\n    void setVariables(QString tokenUrl, QString scope, QString clientId, QString clientSecret);\n\npublic slots:\n    void authenticationNeededCallback() override;\n\n};\n\n//resource owner password flow\nclass OauthPassword : public OauthBase\n{\n    Q_OBJECT\npublic:\n    OauthPassword(QObject *parent = nullptr);\n    void link() override;\n    void unlink() override;\n    void setVariables(QString tokenUrl, QString scope, QString clientId, QString clientSecret, QString username, QString password);\n\nprivate:\n    QString m_username, m_password;\n\npublic slots:\n    void authenticationNeededCallback() override;\n\n};\n\n\n} // namespace RespExtServer\n#endif // OAI_OAUTH2_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIObject.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n#ifndef OAI_OBJECT_H\n#define OAI_OBJECT_H\n\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QMetaType>\n\nnamespace RespExtServer {\n\nclass OAIObject {\npublic:\n    OAIObject() {}\n\n    OAIObject(QString jsonString) {\n        fromJson(jsonString);\n    }\n\n    virtual ~OAIObject() {}\n\n    virtual QJsonObject asJsonObject() const {\n        return jObj;\n    }\n\n    virtual QString asJson() const {\n        QJsonDocument doc(jObj);\n        return doc.toJson(QJsonDocument::Compact);\n    }\n\n    virtual void fromJson(QString jsonString) {\n        QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());\n        jObj = doc.object();\n    }\n\n    virtual void fromJsonObject(QJsonObject json) {\n        jObj = json;\n    }\n\n    virtual bool isSet() const {\n        return false;\n    }\n\n    virtual bool isValid() const {\n        return true;\n    }\n\nprivate:\n    QJsonObject jObj;\n};\n\n} // namespace RespExtServer\n\nQ_DECLARE_METATYPE(RespExtServer::OAIObject)\n\n#endif // OAI_OBJECT_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIServerConfiguration.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/**\n * Representing a Server configuration.\n */\n#ifndef OAI_SERVERVCONFIGURATION_H\n#define OAI_SERVERVCONFIGURATION_H\n\n#include <QString>\n#include <QMap>\n#include <QRegularExpression>\n#include <QUrl>\n#include <stdexcept>\n#include \"OAIServerVariable.h\"\n\nnamespace RespExtServer {\n\nclass OAIServerConfiguration {\npublic:\n    /**\n     * @param url A URL to the target host.\n     * @param description A description of the host designated by the URL.\n     * @param variables A map between a variable name and its value. The value is used for substitution in the server's URL template.\n     */\n    OAIServerConfiguration(const QUrl &url, const QString &description, const QMap<QString, OAIServerVariable> &variables)\n    : _description(description),\n      _variables(variables),\n      _url(url){}\n    OAIServerConfiguration(){}\n    ~OAIServerConfiguration(){}\n\n    /**\n     * Format URL template using given variables.\n     *\n     * @param variables A map between a variable name and its value.\n     * @return Formatted URL.\n     */\n    QString URL() {\n        QString url = _url.toString();\n        if(!_variables.empty()){\n            // go through variables and replace placeholders\n            for (auto const& v : _variables.keys()) {\n                QString name = v;\n                OAIServerVariable serverVariable = _variables.value(v);\n                QString value = serverVariable._defaultValue;\n\n                if (!serverVariable._enumValues.empty() && !serverVariable._enumValues.contains(value)) {\n                    throw std::runtime_error(QString(\"The variable \" + name + \" in the server URL has invalid value \" + value + \".\").toUtf8());\n                }\n                QRegularExpression regex(QString(\"\\\\{\" + name + \"\\\\}\"));\n                url = url.replace(regex, value);\n\n            }\n            return url;\n        }\n        return url;\n    }\n\n    int setDefaultValue(const QString &variable,const QString &value){\n      if(_variables.contains(variable))\n        return _variables[variable].setDefaultValue(value);\n      return -1;\n    }\n\n    QString _description;\n    QMap<QString, OAIServerVariable> _variables;\n    QUrl _url;\n\n};\n\n} // namespace RespExtServer\n\n#endif // OAI_SERVERVCONFIGURATION_H\n"
  },
  {
    "path": "src/modules/extension-server/client/OAIServerVariable.h",
    "content": "/**\n * RESP.app Extension server\n * RESP.app Extension Server API allows you to extend RESP.app with your custom data formatters\n *\n * The version of the OpenAPI document: 2022.0-preview1\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/**\n * Representing a Server Variable for server URL template substitution.\n */\n#ifndef OAI_SERVERVARIABLE_H\n#define OAI_SERVERVARIABLE_H\n#include <QString>\n#include <QSet>\n\nnamespace RespExtServer {\n\nclass OAIServerVariable {\npublic:\n\n    /**\n     * @param description A description for the server variable.\n     * @param defaultValue The default value to use for substitution.\n     * @param enumValues An enumeration of string values to be used if the substitution options are from a limited set.\n     */\n    OAIServerVariable(const QString &description, const QString &defaultValue, const QSet<QString> &enumValues)\n    : _defaultValue(defaultValue),\n      _description(description),\n      _enumValues(enumValues){}\n\n    OAIServerVariable(){}\n    ~OAIServerVariable(){}\n\n    int setDefaultValue(const QString& value){\n      if( _enumValues.contains(value)){\n        _defaultValue = value;\n        return 0;\n      }\n      return -2;\n    }\n\n    QString getDefaultValue(){return _defaultValue;}\n    QSet<QString> getEnumValues(){return _enumValues;}\n\n\n    QString _defaultValue;\n    QString _description;\n    QSet<QString> _enumValues;\n\n};\n\n} // namespace RespExtServer\n\n#endif // OAI_SERVERVARIABLE_H\n"
  },
  {
    "path": "src/modules/extension-server/client/client.pri",
    "content": "QT += network\n\nHEADERS += \\\n# Models\n    $${PWD}/OAIDataFormatter.h \\\n    $${PWD}/OAIDecodePayload.h \\\n    $${PWD}/OAIEncodePayload.h \\\n    $${PWD}/OAIInline_response_400.h \\\n# APIs\n    $${PWD}/OAIDefaultApi.h \\\n# Others\n    $${PWD}/OAIHelpers.h \\\n    $${PWD}/OAIHttpRequest.h \\\n    $${PWD}/OAIObject.h \\\n    $${PWD}/OAIEnum.h \\\n    $${PWD}/OAIHttpFileElement.h \\\n    $${PWD}/OAIServerConfiguration.h \\\n    $${PWD}/OAIServerVariable.h \\\n    $${PWD}/OAIOauth.h\n\nSOURCES += \\\n# Models\n    $${PWD}/OAIDataFormatter.cpp \\\n    $${PWD}/OAIDecodePayload.cpp \\\n    $${PWD}/OAIEncodePayload.cpp \\\n    $${PWD}/OAIInline_response_400.cpp \\\n# APIs\n    $${PWD}/OAIDefaultApi.cpp \\\n# Others\n    $${PWD}/OAIHelpers.cpp \\\n    $${PWD}/OAIHttpRequest.cpp \\\n    $${PWD}/OAIHttpFileElement.cpp \\\n    $${PWD}/OAIOauth.cpp\n"
  },
  {
    "path": "src/modules/extension-server/dataformattermanager.cpp",
    "content": "#include \"dataformattermanager.h\"\n\n#include <QCoreApplication>\n#include <QDebug>\n#include <QDir>\n#include <QDirIterator>\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QSettings>\n\n#include \"app/models/configmanager.h\"\n#include \"client/OAIDefaultApi.h\"\n\nRespExtServer::DataFormattersManager::DataFormattersManager(QQmlApplicationEngine &engine)\n    : m_engine(engine), m_api(new RespExtServer::OAIDefaultApi()) {\n  QSettings settings;\n\n  int requestTimeout =\n      settings.value(\"app/extensionServerRequestTimeout\", 10).toInt() * 1000;\n\n  m_api->setTimeOut(requestTimeout);\n  QObject::connect(m_api.data(), &OAIDefaultApi::dataFormattersGetSignal, this,\n                   &DataFormattersManager::onLoaded);\n\n  QObject::connect(m_api.data(), &OAIDefaultApi::dataFormattersGetSignalE, this,\n                   &DataFormattersManager::onLoadingError);\n\n  QObject::connect(m_api.data(),\n                   &OAIDefaultApi::dataFormattersIdDecodePostSignalFull, this,\n                   &DataFormattersManager::onDecoded);\n\n  QObject::connect(m_api.data(),\n                   &OAIDefaultApi::dataFormattersIdEncodePostSignalFull, this,\n                   &DataFormattersManager::onEncoded);\n\n  QObject::connect(m_api.data(),\n                   &OAIDefaultApi::dataFormattersIdDecodePostSignalEFull, this,\n                   &DataFormattersManager::onDecodeError);\n\n  QObject::connect(m_api.data(),\n                   &OAIDefaultApi::dataFormattersIdEncodePostSignalEFull, this,\n                   &DataFormattersManager::onEncodeError);\n}\n\nvoid RespExtServer::DataFormattersManager::loadFormatters() {\n  m_api->setNewServerForAllOperations(extServerUrl());\n\n  QSettings settings;\n\n  QString user =\n      settings.value(\"app/extensionServerUser\", QString()).toString();\n  QString password =\n      settings.value(\"app/extensionServerPassword\", QString()).toString();\n\n  if (!user.isEmpty() && !password.isEmpty()) {\n    m_api->setUsername(user);\n    m_api->setPassword(password);\n  }\n\n  m_api->dataFormattersGet();\n}\n\nint RespExtServer::DataFormattersManager::rowCount(const QModelIndex &) const {\n  return m_formattersData.size();\n}\n\nQVariant RespExtServer::DataFormattersManager::data(const QModelIndex &index,\n                                                    int role) const {\n  if (!(0 <= index.row() && index.row() < rowCount())) {\n    return QVariant();\n  }\n\n  auto data = m_formattersData[index.row()];\n\n  if (role == name) {\n    return data.getName();\n  } else if (role == id) {\n    return data.getId();\n  } else if (role == keyTypes) {\n    return data.getKeyTypes();\n  } else if (role == magicHeader) {\n    return data.getMagicHeader();\n  } else if (role == readOnly) {\n    return data.isReadOnly();\n  }\n\n  return QVariant();\n}\n\nQHash<int, QByteArray> RespExtServer::DataFormattersManager::roleNames() const {\n  QHash<int, QByteArray> roles;\n  roles[id] = \"id\";\n  roles[name] = \"name\";\n  roles[keyTypes] = \"keyTypes\";\n  roles[magicHeader] = \"magicHeader\";\n  roles[readOnly] = \"readOnly\";\n  return roles;\n}\n\nvoid RespExtServer::DataFormattersManager::setUrl(const QString &path) {\n  m_extServerUrl = path;\n}\n\nvoid RespExtServer::DataFormattersManager::decode(const QString &formatterId,\n                                                  const QByteArray &data,\n                                                  QVariant context,\n                                                  QJSValue jsCallback) {\n  if (!m_mapping.contains(formatterId)) {\n    emit error(QCoreApplication::translate(\"RESP\", \"Can't find formatter: %1\")\n                   .arg(formatterId));\n    return;\n  }\n\n  if (!jsCallback.isCallable() || !context.canConvert<QVariantMap>()) {\n    emit error(QCoreApplication::translate(\"RESP\", \"Invalid callback\"));\n    return;\n  }\n\n  auto requestContext = context.toMap();\n\n  m_context = FormatterContext{jsCallback, formatterId};\n\n  OAIDecodePayload payload;\n  payload.setData(data.toBase64());\n  payload.setRedisKeyName(requestContext[\"redis-key-name\"].toByteArray());\n  payload.setRedisKeyType(requestContext[\"redis-key-type\"].toString());\n\n  m_api->dataFormattersIdDecodePost(formatterId, payload);\n}\n\nvoid RespExtServer::DataFormattersManager::isValid(const QString &formatterId,\n                                                   const QByteArray &data,\n                                                   QVariant context,\n                                                   QJSValue jsCallback) {\n  // TODO: Check magic header if any\n  Q_UNUSED(context);\n  Q_UNUSED(formatterId);\n  Q_UNUSED(data);\n  jsCallback.call(QJSValueList{true});\n}\n\nvoid RespExtServer::DataFormattersManager::encode(const QString &formatterId,\n                                                  const QByteArray &data,\n                                                  QVariant context,\n                                                  QJSValue jsCallback) {\n  if (!m_mapping.contains(formatterId)) {\n    emit error(QCoreApplication::translate(\"RESP\", \"Can't find formatter: %1\")\n                   .arg(formatterId));\n    return;\n  }\n\n  m_context = FormatterContext{jsCallback, formatterId};\n\n  OAIEncodePayload payload;\n  payload.setData(data.toBase64());\n\n  auto requestContext = QJsonDocument::fromVariant(context);\n\n  if (requestContext.isObject()) {\n    OAIObject metadata;\n    metadata.fromJsonObject(requestContext.object());\n    payload.setMetadata(metadata);\n  }\n\n  m_api->dataFormattersIdEncodePost(formatterId, payload);\n}\n\nQVariantList RespExtServer::DataFormattersManager::getPlainList() {\n  QList<QVariant> r;\n  foreach (auto v, m_formattersData) {\n    r.append(v.asJsonObject().toVariantMap());\n  }\n  return r;\n}\n\nQString RespExtServer::DataFormattersManager::extServerUrl() {\n  if (!m_extServerUrl.isEmpty()) {\n    return m_extServerUrl;\n  }\n\n  QSettings settings;\n  return settings.value(\"app/extensionServerUrl\", QString()).toString();\n}\n\nbool RespExtServer::DataFormattersManager::isInstalled(const QString &name) {\n  return m_mapping.contains(name);\n}\n\nvoid RespExtServer::DataFormattersManager::onLoaded(\n    QList<RespExtServer::OAIDataFormatter> summary) {\n  qDebug() << \"Formatters loaded from extension server\" << summary.size();\n\n  emit layoutAboutToBeChanged();\n  m_formattersData = summary;\n  fillMapping();\n  emit layoutChanged();\n  emit loaded();\n}\n\nvoid RespExtServer::DataFormattersManager::onLoadingError(\n    QList<RespExtServer::OAIDataFormatter>, QNetworkReply::NetworkError,\n    QString error_str) {\n  emit error(\n      QCoreApplication::translate(\n          \"RESP\",\n          \"Can't load list of available formatters from extension server: %1\")\n          .arg(error_str));\n}\n\nvoid RespExtServer::DataFormattersManager::onDecoded(\n    OAIHttpRequestWorker *worker, QString) {\n  if (!worker || !m_context.isValid()) return;\n\n  auto headers = worker->getResponseHeaders();\n\n  QString format{\"plain\"};\n  auto decoded = QString::fromUtf8(worker->response);\n\n  if (headers.contains(\"Content-Type\")) {\n    if (headers[\"Content-Type\"].toLower() == \"application/json\") {\n      format = \"json\";\n    } else if (headers[\"Content-Type\"].toLower().startsWith(\"image\")) {\n      format = \"image\";\n      decoded = QString(\"data:%1;base64,%2\")\n                    .arg(headers[\"Content-Type\"])\n                    .arg(QString(worker->response.toBase64()));\n    }\n  }\n\n  auto formatter = m_formattersData[m_mapping[m_context.formatterId]];\n\n  m_context.jsCallback.call(\n      QJSValueList{QString(), decoded, formatter.isReadOnly(), format});\n}\n\nvoid RespExtServer::DataFormattersManager::onEncoded(\n    OAIHttpRequestWorker *worker, QString) {\n  if (!worker || !m_context.isValid()) return;\n\n  auto encoded = m_engine.toScriptValue(worker->response);\n\n  m_context.jsCallback.call(QJSValueList{QString(), encoded});\n}\n\nvoid RespExtServer::DataFormattersManager::onDecodeError(\n    OAIHttpRequestWorker *worker, QNetworkReply::NetworkError,\n    QString error_str) {\n  if (!worker || !m_context.isValid()) return;\n\n  auto formatter = m_formattersData[m_mapping[m_context.formatterId]];\n\n  m_context.jsCallback.call(QJSValueList{error_str, QString(), true, \"plain\"});\n}\n\nvoid RespExtServer::DataFormattersManager::onEncodeError(\n    OAIHttpRequestWorker *worker, QNetworkReply::NetworkError,\n    QString error_str) {\n  if (!worker || !m_context.isValid()) return;\n\n  emit error(QCoreApplication::translate(\"RESP\", \"Can't encode value: %1\")\n                 .arg(error_str));\n}\n\nvoid RespExtServer::DataFormattersManager::fillMapping() {\n  int index = 0;\n\n  for (const auto &f : qAsConst(m_formattersData)) {\n    m_mapping[f.getId()] = index;\n    index++;\n  }\n}\n"
  },
  {
    "path": "src/modules/extension-server/dataformattermanager.h",
    "content": "#pragma once\n#include \"client/OAIDataFormatter.h\"\n#include \"client/OAIHttpRequest.h\"\n\n#include <QAbstractListModel>\n#include <QJSValue>\n#include <QNetworkReply>\n#include <QSharedPointer>\n#include <QQmlApplicationEngine>\n\nnamespace RespExtServer {\nclass OAIDefaultApi;\n\nclass DataFormattersManager : public QAbstractListModel {\n  Q_OBJECT\n\n public:\n  enum Roles { name = Qt::UserRole + 1, id, keyTypes, magicHeader, readOnly };\n\n public:\n  DataFormattersManager(QQmlApplicationEngine& engine);\n\n  int rowCount(const QModelIndex& parent = QModelIndex()) const override;\n\n  QVariant data(const QModelIndex& index, int role) const override;\n\n  QHash<int, QByteArray> roleNames() const override;\n\n  void setUrl(const QString& path);\n\n signals:\n  void error(const QString& msg);\n\n  void loaded();\n\n public:\n  Q_INVOKABLE void loadFormatters();\n\n  Q_INVOKABLE void decode(const QString& formatterId, const QByteArray& data,\n                          QVariant context, QJSValue jsCallback);\n\n  Q_INVOKABLE void isValid(const QString& formatterName, const QByteArray& data,\n                          QVariant context, QJSValue jsCallback);\n\n  Q_INVOKABLE void encode(const QString& formatterId, const QByteArray& data,\n                          QVariant context, QJSValue jsCallback);\n\n  Q_INVOKABLE QVariantList getPlainList();\n\n  Q_INVOKABLE QString extServerUrl();\n\n  Q_INVOKABLE bool isInstalled(const QString& name);\n\n protected slots:\n  void onLoaded(QList<RespExtServer::OAIDataFormatter> summary);\n  void onLoadingError(QList<RespExtServer::OAIDataFormatter> summary,\n                      QNetworkReply::NetworkError error_type,\n                      QString error_str);\n\n  void onDecoded(OAIHttpRequestWorker *worker, QString summary);\n  void onEncoded(OAIHttpRequestWorker *worker, QString summary);\n\n  void onDecodeError(OAIHttpRequestWorker *worker, QNetworkReply::NetworkError error_type, QString error_str);\n  void onEncodeError(OAIHttpRequestWorker *, QNetworkReply::NetworkError, QString error_str);\n\n private:\n  void fillMapping();\n\n  struct FormatterContext {\n    QJSValue jsCallback = QJSValue();\n    QString formatterId = QString();\n    bool decodeValidation = false;\n\n    FormatterContext() {}\n    FormatterContext(QJSValue c, QString f) : jsCallback(c), formatterId(f) {}\n\n    bool isValid() { return jsCallback.isCallable() && !formatterId.isEmpty(); }\n  };\n\n private:\n  QQmlApplicationEngine& m_engine;\n  QList<OAIDataFormatter> m_formattersData;\n  QHash<QString, int> m_mapping;\n  QString m_extServerUrl;\n  QSharedPointer<RespExtServer::OAIDefaultApi> m_api;\n  FormatterContext m_context;\n};\n\n}\n"
  },
  {
    "path": "src/modules/extension-server/generate_client.sh",
    "content": "#!/bin/bash\n\nopenapi-generator generate -i server_spec.yaml -g cpp-qt-client --additional-properties=cppNamespace=RespExtServer  -o .\n"
  },
  {
    "path": "src/modules/server-actions/serverstatsmodel.cpp",
    "content": "#include \"serverstatsmodel.h\"\n#include <qredisclient/redisclient.h>\n#include <QCoreApplication>\n\nServerStats::Model::Model(QSharedPointer<RedisClient::Connection> connection,\n                          int dbIndex, QList<QByteArray>)\n    : TabModel(connection, dbIndex) {\n  m_serverInfoUpdateTimer.setInterval(5000);\n  m_serverInfoUpdateTimer.setSingleShot(false);\n  m_slowLogUpdateTimer.setInterval(5000);\n  m_slowLogUpdateTimer.setSingleShot(false);\n  m_clientsUpdateTimer.setInterval(5000);\n  m_clientsUpdateTimer.setSingleShot(false);\n\n  m_pubSubMonitorConnection = connection->clone();\n\n  QObject::connect(&m_serverInfoUpdateTimer, &QTimer::timeout, this, &Model::srvInfoCallback);\n\n  QObject::connect(&m_slowLogUpdateTimer, &QTimer::timeout, this, &Model::slowLogCallback);\n\n  QObject::connect(&m_clientsUpdateTimer, &QTimer::timeout, this, &Model::clientsCallback);\n\n  QObject::connect(this, &TabModel::initialized, [this]() {\n    srvInfoCallback();\n    m_serverInfoUpdateTimer.start();\n  });\n}\n\nServerStats::Model::~Model() {\n  m_serverInfoUpdateTimer.stop();\n  m_slowLogUpdateTimer.stop();\n  m_clientsUpdateTimer.stop();\n}\n\nQString ServerStats::Model::getName() const {\n  return QCoreApplication::translate(\"RESP\", \"Server %0\")\n      .arg(m_connection->getConfig().name());\n}\n\nQVariantMap ServerStats::Model::serverInfo() const { return m_serverInfo; }\n\nQVariant ServerStats::Model::slowLog() const { return m_slowLog; }\n\nQVariant ServerStats::Model::clients() const { return m_clients; }\n\nQVariant ServerStats::Model::pubSubChannels() const {\n  QVariantList r;\n  for (QByteArray ch : m_pubSubChannels) {\n    r.append(QVariant(ch));\n  }\n  return r;\n}\n\nbool ServerStats::Model::refreshSlowLog() const {\n  return m_slowLogUpdateTimer.isActive();\n}\n\nvoid ServerStats::Model::setRefreshSlowLog(bool v) {\n  if (refreshSlowLog() != v && refreshSlowLog()) m_slowLogUpdateTimer.stop();\n  if (refreshSlowLog() != v && !refreshSlowLog()) {\n      slowLogCallback();\n      m_slowLogUpdateTimer.start();\n  }\n}\n\nbool ServerStats::Model::refreshClients() const {\n  return m_clientsUpdateTimer.isActive();\n}\n\nvoid ServerStats::Model::setRefreshClients(bool v) {\n  if (refreshClients() != v && refreshClients()) m_clientsUpdateTimer.stop();\n  if (refreshClients() != v && !refreshClients()) {\n      clientsCallback();\n      m_clientsUpdateTimer.start();\n  }\n}\n\nbool ServerStats::Model::refreshPubSubMonitor() const {\n  return m_pubSubMonitorConnection->isConnected();\n}\n\nvoid ServerStats::Model::setRefreshPubSubMonitor(bool v) {\n  if (m_pubSubMonitorConnection->isConnected() && !v) {\n    m_pubSubMonitorConnection->disconnect();\n    return;\n  }\n\n  if (!m_pubSubMonitorConnection->isConnected() && v) {\n    m_pubSubMonitorConnection->cmd(\n        {\"PSUBSCRIBE\", \"*\"}, this, -1,\n        [this](const RedisClient::Response& result) {\n          if (result.type() != RedisClient::Response::Array) {\n            return;\n          }\n\n          QVariantList msg = result.value().toList();\n\n          if (msg.size() == 4) {\n            m_pubSubChannels.insert(msg[2].toByteArray());            \n          }\n          emit pubSubChannelsChanged();\n        },\n        [this](const QString& e) { cmdErrorHander(e); });\n  }\n}\n\nvoid ServerStats::Model::subscribeToChannel(const QString &c)\n{\n    emit openConsoleTerminal(m_connection, m_dbIndex, true, {\"SUBSCRIBE\", c.toUtf8()});\n}\n\nvoid ServerStats::Model::monitorCommands()\n{\n    emit openConsoleTerminal(m_connection, m_dbIndex, true, {\"MONITOR\"});\n}\n\nvoid ServerStats::Model::openTerminal()\n{\n    emit openConsoleTerminal(m_connection, m_dbIndex, true, {});\n}\n\nvoid ServerStats::Model::cmdErrorHander(const QString& err) { emit error(err); }\n\nvoid ServerStats::Model::srvInfoCallback() {\n  m_connection->cmd(\n      {\"INFO\", \"all\"}, this, -1,\n      [this](const RedisClient::Response& r) {\n        m_serverInfo = RedisClient::ServerInfo::fromString(\n                           QString::fromUtf8(r.value().toByteArray()))\n                           .parsed.toVariantMap();\n        emit serverInfoChanged();\n      },\n      [this](const QString& e) { cmdErrorHander(e); });\n}\n\nvoid ServerStats::Model::slowLogCallback() {\n  m_connection->cmd(\n      {\"SLOWLOG\", \"GET\", \"15\"}, this, -1,\n      [this](const RedisClient::Response& r) {\n        QVariantList processed;\n\n        for (QVariant item : r.value().toList()) {\n          auto itemList = item.toList();\n          QVariantMap row;\n          row.insert(\"time\", itemList[1]);\n          row.insert(\"exec_time\", itemList[2]);\n          row.insert(\"cmd\", itemList[3]);\n          processed.append(row);\n        }\n\n        m_slowLog = processed;\n        emit slowLogChanged();\n      },\n      [this](const QString& e) { cmdErrorHander(e); });\n}\n\nvoid ServerStats::Model::clientsCallback() {\n  m_connection->cmd(\n      {\"CLIENT\", \"LIST\"}, this, -1,\n      [this](const RedisClient::Response& r) {\n        QVariant result = r.value();\n        QStringList lines = result.toString().split(\"\\n\");\n\n        QVariantList parsedClients;\n\n        for (auto rawLine : lines) {\n          QStringList lineParts = rawLine.split(\" \");\n          QVariantMap parsed;\n\n          for (auto linePart : lineParts) {\n            QStringList keyAndVal = linePart.split(\"=\");\n\n            if (keyAndVal.size() > 1) {\n              parsed.insert(keyAndVal[0], keyAndVal[1]);\n            } else if (linePart.size() > 0) {\n              parsed.insert(keyAndVal[0], \"\");\n            }\n          }\n          if (parsed.size() > 0)\n            parsedClients.append(parsed);\n        }\n\n        m_clients = parsedClients;\n        emit clientsChanged();\n      },\n      [this](const QString& e) { cmdErrorHander(e); });\n}\n"
  },
  {
    "path": "src/modules/server-actions/serverstatsmodel.h",
    "content": "#pragma once\n#include \"common/tabviewmodel.h\"\n#include \"exception.h\"\n\nnamespace ServerStats {\n\nclass Model : public TabModel {\n  Q_OBJECT\n  ADD_EXCEPTION\n\n  Q_PROPERTY(QVariantMap serverInfo READ serverInfo NOTIFY serverInfoChanged)\n\n  Q_PROPERTY(QVariant slowLog READ slowLog NOTIFY slowLogChanged)\n  Q_PROPERTY(bool refreshSlowLog READ refreshSlowLog WRITE setRefreshSlowLog)\n\n  Q_PROPERTY(QVariant clients READ clients NOTIFY clientsChanged)\n  Q_PROPERTY(bool refreshClients READ refreshClients WRITE setRefreshClients)\n\n  Q_PROPERTY(\n      QVariant pubSubChannels READ pubSubChannels NOTIFY pubSubChannelsChanged)\n  Q_PROPERTY(bool refreshPubSubMonitor READ refreshPubSubMonitor WRITE\n                 setRefreshPubSubMonitor)\n\n public:\n  Model(QSharedPointer<RedisClient::Connection> connection, int dbIndex, QList<QByteArray>);\n\n  ~Model() override;\n\n  QString getName() const override;\n\n  QVariantMap serverInfo() const;\n\n  QVariant slowLog() const;\n\n  QVariant clients() const;\n\n  QVariant pubSubChannels() const;\n\n  bool refreshSlowLog() const;\n  void setRefreshSlowLog(bool v);\n\n  bool refreshClients() const;\n  void setRefreshClients(bool v);\n\n  bool refreshPubSubMonitor() const;\n  void setRefreshPubSubMonitor(bool v);\n\n  Q_INVOKABLE void subscribeToChannel(const QString& c);\n  Q_INVOKABLE void monitorCommands();\n  Q_INVOKABLE void openTerminal();\n\n signals:\n  void serverInfoChanged();\n  void slowLogChanged();\n  void clientsChanged();\n  void pubSubChannelsChanged();\n  void openConsoleTerminal(QSharedPointer<RedisClient::Connection> c,\n                           int db, bool inNewTab, QList<QByteArray> cmd);\n\n protected:\n  void cmdErrorHander(const QString& err);\n\n protected slots:\n  void srvInfoCallback();\n\n  void slowLogCallback();\n\n  void clientsCallback();\n\n private:\n  QTimer m_serverInfoUpdateTimer;\n  QTimer m_slowLogUpdateTimer;\n  QTimer m_clientsUpdateTimer;\n  QSharedPointer<RedisClient::Connection> m_pubSubMonitorConnection;\n  QVariantMap m_serverInfo;\n  QVariant m_slowLog;\n  QVariant m_clients;\n  QSet<QByteArray> m_pubSubChannels;\n};\n}  // namespace ServerStats\n"
  },
  {
    "path": "src/modules/value-editor/abstractkeyfactory.h",
    "content": "#pragma once\r\n\r\n#include <QSharedPointer>\r\n#include <QString>\r\n#include <QVariantMap>\r\n#include <functional>\r\n#include \"keymodel.h\"\r\n\r\nnamespace RedisClient {\r\nclass Connection;\r\n}\r\n\r\nnamespace ValueEditor {\r\n\r\nclass AbstractKeyFactory {\r\n public:\r\n  virtual ~AbstractKeyFactory() {}\r\n\r\n  virtual void loadKey(\r\n      QSharedPointer<RedisClient::Connection> connection,\r\n      QByteArray keyFullPath, int dbIndex,\r\n      std::function<void(QSharedPointer<Model>, const QString&)> callback) = 0;\r\n};\r\n\r\n}  // namespace ValueEditor\r\n"
  },
  {
    "path": "src/modules/value-editor/embeddedformattersmanager.cpp",
    "content": "#include \"embeddedformattersmanager.h\"\n\n#include <qpython.h>\n\n#include <QCoreApplication>\n#include <QDebug>\n#include <QDir>\n#include <QDirIterator>\n#include <QJsonDocument>\n#include <QJsonObject>\n\n#include \"app/models/configmanager.h\"\n\nValueEditor::EmbeddedFormattersManager::EmbeddedFormattersManager()\n    : m_python(nullptr) {}\n\nvoid ValueEditor::EmbeddedFormattersManager::init(QSharedPointer<QPython> p) {\n  if (!p) {\n    emit error(\"Failed to load python\");\n    return;\n  }\n\n  m_python = p;\n\n  QObject::connect(m_python.data(), &QPython::error, this,\n                   &EmbeddedFormattersManager::error);\n}\n\nvoid ValueEditor::EmbeddedFormattersManager::loadFormattersModule(\n    QJSValue callback) {\n  if (!m_python) {\n    qWarning() << \"EmbeddedFormattersManager is not ready\";\n    return;\n  }\n\n  m_python->importModule(\"formatters\", callback);\n}\n\nvoid ValueEditor::EmbeddedFormattersManager::loadFormatters(QJSValue callback) {\n  pythonCall(\"formatters.get_formatters_list\", QVariantList(), callback);\n}\n\nvoid ValueEditor::EmbeddedFormattersManager::decode(\n    const QString &formatterName, const QByteArray &data, QJSValue jsCallback) {\n  pythonCall(\"formatters.decode\", QVariantList{formatterName, data},\n             jsCallback);\n}\n\nvoid ValueEditor::EmbeddedFormattersManager::isValid(\n    const QString &formatterName, const QByteArray &data, QJSValue jsCallback) {\n  pythonCall(\"formatters.validate\", QVariantList{formatterName, data},\n             jsCallback);\n}\n\nvoid ValueEditor::EmbeddedFormattersManager::encode(\n    const QString &formatterName, const QByteArray &data, QJSValue jsCallback) {\n  pythonCall(\"formatters.encode\", QVariantList{formatterName, data},\n             jsCallback);\n}\n\nvoid ValueEditor::EmbeddedFormattersManager::pythonCall(\n    const QString &callable_name, const QVariantList &args,\n    QJSValue jsCallback) {\n  if (!m_python) {\n    qWarning() << \"EmbeddedFormattersManager is not ready\";\n    return;\n  }\n  m_python->call(callable_name, args, jsCallback);\n}\n"
  },
  {
    "path": "src/modules/value-editor/embeddedformattersmanager.h",
    "content": "#pragma once\n#include <QAbstractListModel>\n#include <QJSValue>\n#include <QSharedPointer>\n\nclass QPython;\n\nnamespace ValueEditor {\n\nclass EmbeddedFormattersManager : public QObject {\n  Q_OBJECT\n\n public:\n  enum Roles { name = Qt::UserRole + 1, version, description, cmd };\n\n public:\n  EmbeddedFormattersManager();\n\n  void init(QSharedPointer<QPython> p);\n\n signals:\n  void error(const QString& msg);\n\n public:\n  Q_INVOKABLE void loadFormattersModule(QJSValue callback);\n\n  Q_INVOKABLE void loadFormatters(QJSValue callback);\n\n  Q_INVOKABLE void decode(const QString& formatterName, const QByteArray& data,\n                          QJSValue jsCallback);\n\n  Q_INVOKABLE void isValid(const QString& formatterName, const QByteArray& data,\n                           QJSValue jsCallback);\n\n  Q_INVOKABLE void encode(const QString& formatterName, const QByteArray& data,\n                          QJSValue jsCallback);\n\n protected:\n  void pythonCall(const QString& callable_name, const QVariantList& args,\n                  QJSValue jsCallback);\n\n private:\n  QSharedPointer<QPython> m_python;\n};\n\n}  // namespace ValueEditor\n"
  },
  {
    "path": "src/modules/value-editor/keymodel.h",
    "content": "#pragma once\n#include <qredisclient/connection.h>\n#include <QEnableSharedFromThis>\n#include <QHash>\n#include <QObject>\n#include <QString>\n#include <functional>\n#include \"exception.h\"\n\nnamespace ValueEditor {\n\nclass ModelSignals : public QObject {\n  Q_OBJECT\n public:\n  ModelSignals() {}\n signals:\n  void removed();\n  void error(const QString&);\n};\n\nclass Model : public QEnableSharedFromThis<Model> {\n public:\n  typedef std::function<void(const QString&)> Callback;\n\n  Model() {}\n  virtual QString getKeyName() = 0;\n  virtual QString getKeyTitle(int limit = -1) = 0;\n\n  virtual QString type() = 0;\n  virtual long long getTTL() = 0;\n  virtual QStringList getColumnNames() = 0;\n  virtual QHash<int, QByteArray> getRoles() = 0;\n  virtual QVariant getData(int rowIndex, int dataRole) = 0;\n\n  virtual void setKeyName(const QByteArray&, Callback) = 0;  // async\n  virtual void setTTL(const long long, Callback) = 0;        // async\n  virtual void persistKey(Callback) = 0;        // async\n  virtual void removeKey(Callback) = 0;\n\n  // rows operations\n  virtual void addRow(const QVariantMap&, Callback) = 0;\n  virtual void updateRow(int rowIndex, const QVariantMap&,\n                         Callback) = 0;  // async\n  virtual unsigned long rowsCount() = 0;\n\n  //filters\n  virtual QVariant filter(const QString& key) const = 0;\n  virtual void setFilter(const QString&, QVariant) = 0;\n\n  typedef std::function<void(const QString&, unsigned long)> LoadRowsCallback;\n  virtual void loadRows(QVariant rowStart, unsigned long count,\n                        LoadRowsCallback c) = 0;  // async\n\n  virtual void clearRowCache() = 0;\n  virtual void removeRow(int, Callback) = 0;  // async\n  virtual bool isRowLoaded(int) = 0;\n  virtual bool isMultiRow() const = 0;\n  virtual void loadRowsCount(Callback callback) = 0;\n\n  virtual QSharedPointer<ModelSignals> getConnector() const = 0;\n  virtual QSharedPointer<RedisClient::Connection> getConnection() const = 0;\n  virtual unsigned int dbIndex() const = 0;\n  virtual QString getDefaultFormatter() const = 0;\n\n  virtual ~Model() {}\n};\n\n}  // namespace ValueEditor\n"
  },
  {
    "path": "src/modules/value-editor/largetextmodel.cpp",
    "content": "#include \"largetextmodel.h\"\n#include <QDebug>\n\nValueEditor::LargeTextWrappingModel::LargeTextWrappingModel(const QString &text,\n                                                            uint chunkSize)\n    : m_chunkSize(chunkSize) {\n  setText(text);\n}\n\nValueEditor::LargeTextWrappingModel::~LargeTextWrappingModel() {}\n\nQHash<int, QByteArray> ValueEditor::LargeTextWrappingModel::roleNames() const {\n  QHash<int, QByteArray> roles;\n  roles[Qt::UserRole + 1] = \"value\";\n  return roles;\n}\n\nint ValueEditor::LargeTextWrappingModel::rowCount(const QModelIndex &) const {\n  return m_textRows.size();\n}\n\nQVariant ValueEditor::LargeTextWrappingModel::data(const QModelIndex &index,\n                                                   int role) const {\n  if (!isIndexValid(index)) return QVariant();\n\n  if (role == Qt::UserRole + 1) {\n    return m_textRows[index.row()];\n  }\n\n  return QVariant();\n}\n\nvoid ValueEditor::LargeTextWrappingModel::setText(const QString &text) {\n  m_textRows.reserve(text.size() / m_chunkSize);\n\n  for (uint chunkIndex = 0; chunkIndex < text.size() / m_chunkSize + 1;\n       chunkIndex++) {\n    m_textRows.append(text.mid(chunkIndex * m_chunkSize, m_chunkSize));\n  }\n}\n\nvoid ValueEditor::LargeTextWrappingModel::cleanUp() {\n  emit beginRemoveRows(QModelIndex(), 0, rowCount() - 1);\n  m_textRows.clear();\n  emit endRemoveRows();\n}\n\nQString ValueEditor::LargeTextWrappingModel::getText() {\n  QString result;\n  result.reserve(m_textRows.size() * m_chunkSize);\n\n  for (auto textRow : m_textRows) {\n    result.append(textRow);\n  }\n\n  return result;\n}\n\nvoid ValueEditor::LargeTextWrappingModel::setTextChunk(uint row, QString text) {\n  if (row < m_textRows.size()) {\n    m_textRows[row] = text;\n    emit dataChanged(createIndex(row, 0), createIndex(row, 0));\n  }\n}\n\n/**\n * @brief ValueEditor::LargeTextWrappingModel::searchText\n * @param p\n * @param from\n * @param regex\n * @return\n * 1: TargetTextView\n * 2: Raw Position\n * 3: Relative position for search in TargetTextView\n * 4. Length\n */\nQVariantList ValueEditor::LargeTextWrappingModel::searchText(QString p, int from, bool regex)\n{\n    QString text = getText();\n\n    if (from < 0) {\n        from = 0;\n    }\n\n    qDebug() << \"Search params:\" << p << from << regex;\n\n    int res;\n    int length = 0;\n\n    if (regex) {\n        auto rx = QRegExp(p);\n        res = text.indexOf(rx, from);\n        length = rx.matchedLength();\n    } else {\n        res = text.indexOf(p, from, Qt::CaseInsensitive);\n        length = p.size();\n    }\n\n    if (res == -1) {        \n        return QVariantList {-1, -1, -1, -1};\n    } else {        \n        int row = (int)res / m_chunkSize;\n        return QVariantList {row, res, res % m_chunkSize, length};\n    }\n}\n\nbool ValueEditor::LargeTextWrappingModel::isIndexValid(\n    const QModelIndex &index) const {\n  return 0 <= index.row() && index.row() < rowCount();\n}\n"
  },
  {
    "path": "src/modules/value-editor/largetextmodel.h",
    "content": "#pragma once\n#include <QAbstractListModel>\n#include <QHash>\n#include <QList>\n#include <QSharedPointer>\n\nnamespace ValueEditor {\n\nclass LargeTextWrappingModel : public QAbstractListModel {\n  // TODO(u_glide): Process out of memory exceptions\n\n  Q_OBJECT\n public:\n  LargeTextWrappingModel(const QString &text = QString(),\n                         uint chunkSize = 10000);\n\n  ~LargeTextWrappingModel();\n\n  QHash<int, QByteArray> roleNames() const override;\n\n  int rowCount(const QModelIndex &parent = QModelIndex()) const override;\n\n  QVariant data(const QModelIndex &index, int role) const override;\n\n  void setText(const QString &text);\n\n public slots:\n  void cleanUp();\n\n  QString getText();\n\n  void setTextChunk(uint row, QString text);\n\n  QVariantList searchText(QString p, int from = 0, bool regex=false);\n\n private:\n  bool isIndexValid(const QModelIndex &index) const;\n\n private:\n  uint m_chunkSize;\n  QList<QString> m_textRows;\n};\n\n}  // namespace ValueEditor\n"
  },
  {
    "path": "src/modules/value-editor/syntaxhighlighter.cpp",
    "content": "#include \"syntaxhighlighter.h\"\n#include \"textcharformat.h\"\n\n\nSyntaxHighlighter::SyntaxHighlighter( QObject* parent ) :\n    QSyntaxHighlighter(parent),\n    m_TextDocument(nullptr)\n{\n}\n\nvoid SyntaxHighlighter::highlightBlock( const QString &text )\n{\n    emit highlightBlock( QVariant(text) );\n}\n\nQQuickTextDocument* SyntaxHighlighter::textDocument() const\n{\n    return m_TextDocument;\n}\n\nvoid SyntaxHighlighter::setTextDocument( QQuickTextDocument* textDocument )\n{\n    if (textDocument == m_TextDocument)\n    {\n        return;\n    }\n\n    m_TextDocument = textDocument;\n\n    QTextDocument* doc = m_TextDocument->textDocument();\n    setDocument(doc);\n\n    emit textDocumentChanged();\n}\n\nvoid SyntaxHighlighter::setFormat( int start, int count, const QVariant& format )\n{\n    TextCharFormat* charFormat = qvariant_cast<TextCharFormat*>( format );\n    if ( charFormat )\n    {\n        QSyntaxHighlighter::setFormat( start, count, *charFormat );\n        return;\n    }\n\n    if ( format.canConvert(QVariant::Color) )\n    {\n        QSyntaxHighlighter::setFormat( start, count, format.value<QColor>() );\n        return;\n    }\n\n    if ( format.canConvert(QVariant::Font) )\n    {\n        QSyntaxHighlighter::setFormat( start, count, format.value<QFont>() );\n        return;\n    }\n}\n"
  },
  {
    "path": "src/modules/value-editor/syntaxhighlighter.h",
    "content": "#pragma once\n#include <QObject>\n#include <QTextDocument>\n#include <QSyntaxHighlighter>\n#include <QQuickTextDocument>\n\n/***\n * Based on https://github.com/stephenquan/QtSyntaxHighlighterApp\n *\n * Original code is licensed under the Apache License, Version 2.0\n * (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n * */\n\nclass SyntaxHighlighter : public QSyntaxHighlighter\n{\n    Q_OBJECT\n    Q_PROPERTY(QQuickTextDocument* textDocument READ textDocument WRITE setTextDocument NOTIFY textDocumentChanged)\n\npublic:\n    SyntaxHighlighter(QObject* parent = nullptr);\n\n    Q_INVOKABLE void setFormat(int start, int count, const QVariant& format);\n\nsignals:\n    void textDocumentChanged();\n    void highlightBlock(const QVariant& text);\n\nprotected:\n    QQuickTextDocument* m_TextDocument;\n\n    QQuickTextDocument* textDocument() const;\n    void setTextDocument(QQuickTextDocument* textDocument);\n\n    virtual void highlightBlock(const QString &text);\n};\n"
  },
  {
    "path": "src/modules/value-editor/tabsmodel.cpp",
    "content": "#include \"tabsmodel.h\"\n#include <qredisclient/connection.h>\n#include <qredisclient/utils/text.h>\n#include <QCoreApplication>\n#include <QDebug>\n#include <QQmlEngine>\n#include <QtConcurrent>\n#include \"app/events.h\"\n#include \"connections-tree/items/keyitem.h\"\n#include \"value-editor/valueviewmodel.h\"\n\n\n#define TAB_NAME_LIMIT 30\n\nValueEditor::TabsModel::TabsModel(QSharedPointer<AbstractKeyFactory> keyFactory,\n                                  QSharedPointer<Events> events)\n    : m_keyFactory(keyFactory), m_events(events), m_currentTabIndex(0) {}\n\nValueEditor::TabsModel::~TabsModel() { m_viewModels.clear(); }\n\nvoid ValueEditor::TabsModel::openTab(\n    QSharedPointer<RedisClient::Connection> connection,\n    QSharedPointer<ConnectionsTree::KeyItem> key, bool inNewTab) {\n\n  auto viewModel = createViewModel(\n      QString(QCoreApplication::translate(\"RESP\", \"Loading key: %1 from db %2\"))\n          .arg(QString::fromUtf8(key->getFullPath()))\n          .arg(key->getDbIndex()),\n      key.toWeakRef());\n\n  QSharedPointer<RedisClient::Connection> conn;  \n\n  if (inNewTab || m_viewModels.count() == 0) {    \n    beginInsertRows(QModelIndex(), m_viewModels.count(), m_viewModels.count());\n    m_viewModels.append(viewModel);\n    endInsertRows();\n  } else {\n    emit layoutAboutToBeChanged();\n\n    if (!(0 <= m_currentTabIndex && m_currentTabIndex < rowCount())) {\n      m_currentTabIndex = rowCount() - 1;\n    }\n\n    auto oldModel = m_viewModels[m_currentTabIndex];\n    m_viewModels.replace(m_currentTabIndex, viewModel);\n    emit layoutChanged();\n    emit replaceTab(m_currentTabIndex);\n\n    auto keyModel = oldModel->model();\n\n    bool reuseConnection =\n        (keyModel && keyModel->getConnection()->getConfig().id() ==\n                         connection->getConfig().id());\n\n    if (reuseConnection) {\n      conn = keyModel->getConnection();\n    }\n\n    oldModel.clear();\n  }\n\n  auto viewModelWeekRef = viewModel.toWeakRef();\n\n  auto loadingHandler = [this, viewModelWeekRef](QSharedPointer<Model> keyModel,\n                                                 const QString& error) {\n    if (keyModel.isNull() || !error.isEmpty()) {\n      emit tabError(-1, QString(\"<b>%1</b>:\\n%2\")\n                            .arg(QCoreApplication::translate(\n                                \"RESP\", \"Cannot open value tab\"))\n                            .arg(error));\n      return;\n    }\n\n    auto viewModel = viewModelWeekRef.toStrongRef();\n\n    if (viewModel) {\n      viewModel->setModel(keyModel);\n    }\n  };\n\n  auto callbackWrapper = [loadingHandler](QSharedPointer<Model> keyModel,\n                                          const QString& error) {\n    QTimer::singleShot(1, [=]() { loadingHandler(keyModel, error); });\n  };\n\n  if (!conn) {\n    conn = connection->clone();\n    conn->disableAutoConnect();\n    m_events->registerLoggerForConnection(*conn);\n  }\n\n  viewModel->setConnection(conn);\n\n  connect(conn.data(), &RedisClient::Connection::shutdownStart,\n          this, [this, viewModel](){\n      if (!viewModel) return;\n     viewModel->setTabError(QCoreApplication::translate(\"RESP\", \"Connection error\"));\n\n     int modelIndex = m_viewModels.indexOf(viewModel);\n\n     if (modelIndex != -1) {\n         emit dataChanged(index(modelIndex, 0), index(modelIndex, 0));\n     }\n  });\n\n  try {\n    QtConcurrent::run([this, conn, key, viewModelWeekRef, callbackWrapper]() {\n      if (!conn->isConnected())\n        conn->connect();\n\n      m_keyFactory->loadKey(conn, key->getFullPath(), key->getDbIndex(),\n                            callbackWrapper);\n    });\n  } catch (...) {\n    emit tabError(-1, QCoreApplication::translate(\n                          \"RESP\", \"Connection error. Can't open value tab. \"));\n  }\n}\n\nvoid ValueEditor::TabsModel::closeDbKeys(\n    QSharedPointer<RedisClient::Connection> connection, int dbIndex,\n    const QRegExp& filter) {\n  for (int index = 0; 0 <= index && index < m_viewModels.size(); index++) {\n    auto model = m_viewModels.at(index)->model();\n\n    if (!model) continue;\n\n    bool tabMatch =\n        (model->getConnection()->getConfig().id() ==\n             connection->getConfig().id() &&\n         model->dbIndex() == dbIndex && model->getKeyName().contains(filter));\n\n    if (tabMatch) {\n      beginRemoveRows(QModelIndex(), index, index);\n      auto model = m_viewModels[index];      \n      m_viewModels.removeAt(index);\n      endRemoveRows();\n      index--;\n      model.clear();\n    }\n  }\n}\n\nQModelIndex ValueEditor::TabsModel::index(int row, int column,\n                                          const QModelIndex& parent) const {\n  Q_UNUSED(parent);\n\n  if (row < 0 || column < 0) return QModelIndex();\n\n  return createIndex(row, 0);\n}\n\nint ValueEditor::TabsModel::rowCount(const QModelIndex&) const {\n  return m_viewModels.count();\n}\n\nQVariant ValueEditor::TabsModel::data(const QModelIndex& index,\n                                      int role) const {\n  if (!isIndexValid(index)) return QVariant();\n\n  QSharedPointer<Model> model = m_viewModels.at(index.row())->model();\n\n  if (!model) {\n    switch (role) {\n      case keyIndex:\n        return index.row();\n      case showLoader:\n        return true;\n      case tabName:\n        return m_viewModels.at(index.row())->tabLoadingTitle();\n    }\n    return QVariant();\n  }\n\n  switch (role) {\n    case keyIndex:\n      return index.row();\n    case keyNameRole:\n      return model->getKeyName();\n    case tabName:\n      return model->getKeyTitle(TAB_NAME_LIMIT);\n    case keyTTL:\n      return model->getTTL();\n    case keyType:\n      return model->type();\n    case rowsCount:\n      return (qlonglong)model->rowsCount();\n    case isMultiRow:\n      return model->isMultiRow();\n    case showLoader:\n      return false;\n    case defaultFormatter:\n      return model->getDefaultFormatter();\n    case keyModel:\n      QObject* modelPtr =\n          static_cast<QObject*>(m_viewModels.at(index.row()).data());\n      QQmlEngine::setObjectOwnership(modelPtr, QQmlEngine::CppOwnership);\n\n      return QVariant::fromValue(modelPtr);\n  }\n\n  return QVariant();\n}\n\nQHash<int, QByteArray> ValueEditor::TabsModel::roleNames() const {\n  QHash<int, QByteArray> roles;\n  roles[keyIndex] = \"keyIndex\";\n  roles[keyNameRole] = \"keyName\";\n  roles[keyTTL] = \"keyTtl\";\n  roles[keyType] = \"keyType\";\n  roles[isMultiRow] = \"isMultiRow\";\n  roles[rowsCount] = \"keyRowsCount\";\n  roles[keyModel] = \"keyViewModel\";\n  roles[showLoader] = \"showLoader\";\n  roles[tabName] = \"tabName\";\n  roles[defaultFormatter] = \"defaultFormatter\";\n  return roles;\n}\n\nvoid ValueEditor::TabsModel::closeTab(int i) {\n  if (!isIndexValid(index(i, 0))) return;\n\n  beginRemoveRows(QModelIndex(), i, i);\n  auto model = m_viewModels[i];\n  m_viewModels.removeAt(i);\n  endRemoveRows();\n\n  model->close();\n  model.clear();\n}\n\nvoid ValueEditor::TabsModel::setCurrentTab(int i) {\n    if (0 <= i && i < rowCount()) {\n        m_currentTabIndex = i;\n    }\n}\n\nbool ValueEditor::TabsModel::isIndexValid(const QModelIndex& index) const {\n  return 0 <= index.row() && index.row() < rowCount();\n}\n\nvoid ValueEditor::TabsModel::tabChanged(\n    QSharedPointer<ValueEditor::ValueViewModel> m) {\n  int modelIndex = m_viewModels.lastIndexOf(m);\n\n  if (modelIndex == -1) return;\n\n  emit dataChanged(index(modelIndex, 0), index(modelIndex, 0));\n}\n\nvoid ValueEditor::TabsModel::tabRemoved(\n    QSharedPointer<ValueEditor::ValueViewModel> m) {\n  int modelIndex = m_viewModels.lastIndexOf(m);\n\n  if (modelIndex == -1) return;\n\n  beginRemoveRows(QModelIndex(), modelIndex, modelIndex);\n  auto oldModel = m_viewModels[modelIndex];\n  m_viewModels.removeAt(modelIndex);\n  endRemoveRows();\n  oldModel->close();\n  oldModel.clear();\n}\n\nQSharedPointer<ValueEditor::ValueViewModel> ValueEditor::TabsModel::createViewModel(\n    const QString& loadingBanner, QWeakPointer<ConnectionsTree::KeyItem> key) {\n  QSharedPointer<ValueViewModel> viewModel = QSharedPointer<ValueViewModel>(\n      new ValueViewModel(loadingBanner), &QObject::deleteLater);\n\n  auto wPtr = viewModel.toWeakRef();\n\n  connect(viewModel.data(), &ValueViewModel::rowsLoaded, this,\n          [this, wPtr](int, int) {\n            auto viewModel = wPtr.toStrongRef();\n            if (!wPtr) return;\n            tabChanged(viewModel);\n          });\n\n  connect(viewModel.data(), &ValueViewModel::modelLoaded, this, [this, wPtr]() {\n    auto viewModel = wPtr.toStrongRef();\n    if (!viewModel) return;\n    tabChanged(viewModel);\n  });\n\n  connect(viewModel.data(), &ValueViewModel::keyRenamed, this,\n          [this, wPtr, key] {\n            auto viewModel = wPtr.toStrongRef();\n            if (!viewModel) return;\n\n            tabChanged(viewModel);\n            if (key && viewModel->model())\n              key.toStrongRef()->setFullPath(\n                  viewModel->model()->getKeyName().toUtf8());\n          });\n\n  connect(viewModel.data(), &ValueViewModel::keyTTLChanged, this, [this, wPtr] {\n    auto viewModel = wPtr.toStrongRef();\n    if (!viewModel) return;\n    tabChanged(viewModel);\n  });\n\n  connect(viewModel.data(), &ValueViewModel::keyRemoved, this,\n          [this, wPtr, key] {\n            auto viewModel = wPtr.toStrongRef();\n            if (!viewModel) return;\n\n            tabRemoved(viewModel);\n\n            if (key) key.toStrongRef()->setRemoved();\n          });\n\n  return viewModel;\n}\n"
  },
  {
    "path": "src/modules/value-editor/tabsmodel.h",
    "content": "#pragma once\n#include <QAbstractListModel>\n#include <QByteArray>\n#include <QPair>\n#include <QSharedPointer>\n#include <QString>\n#include <functional>\n#include \"abstractkeyfactory.h\"\n#include \"valueviewmodel.h\"\n\nclass Events;\n\nnamespace ConnectionsTree {\nclass KeyItem;\n}\n\nnamespace ValueEditor {\n\nclass TabsModel : public QAbstractListModel {\n  Q_OBJECT\n\n public:\n  enum Roles {\n    keyNameRole = Qt::UserRole + 1,\n    keyIndex,\n    keyTTL,\n    keyType,\n    isMultiRow,\n    rowsCount,\n    keyModel,\n    showLoader,\n    tabName,\n    defaultFormatter\n  };\n\n public:\n  TabsModel(QSharedPointer<AbstractKeyFactory> keyFactory,\n            QSharedPointer<Events> events);\n\n  ~TabsModel() override;\n\n  QModelIndex index(int row, int column = 0,\n                    const QModelIndex& parent = QModelIndex()) const override;\n  int rowCount(const QModelIndex& parent = QModelIndex()) const override;\n  QVariant data(const QModelIndex& index,\n                int role = Qt::DisplayRole) const override;\n  QHash<int, QByteArray> roleNames() const override;\n\n public:  // methods exported to QML\n  Q_INVOKABLE void closeTab(int i);\n  Q_INVOKABLE void setCurrentTab(int i);\n\n signals:\n  void tabError(int index, const QString& error);\n  void replaceTab(int index);\n\n public slots:\n  void openTab(QSharedPointer<RedisClient::Connection> connection,\n               QSharedPointer<ConnectionsTree::KeyItem> key, bool inNewTab);\n  void closeDbKeys(QSharedPointer<RedisClient::Connection> connection,\n                   int dbIndex, const QRegExp& filter);\n\n private:\n  QList<QSharedPointer<ValueViewModel>> m_viewModels;\n  QSharedPointer<AbstractKeyFactory> m_keyFactory;\n  QSharedPointer<Events> m_events;\n  int m_currentTabIndex;\n\n  bool isIndexValid(const QModelIndex& index) const;\n  QSharedPointer<ValueViewModel> createViewModel(const QString& loadingBanner,\n                 QWeakPointer<ConnectionsTree::KeyItem> key);\n  void tabChanged(QSharedPointer<ValueViewModel> m);\n  void tabRemoved(QSharedPointer<ValueViewModel> m);\n};\n\n}  // namespace ValueEditor\n"
  },
  {
    "path": "src/modules/value-editor/textcharformat.cpp",
    "content": "#include \"textcharformat.h\"\n\nTextCharFormat::TextCharFormat(QObject* parent) :\n    QObject(parent)\n{\n}\n\nvoid TextCharFormat::setFont( const QFont& font )\n{\n    if ( font == QTextCharFormat::font() )\n    {\n        return;\n    }\n\n    QTextCharFormat::setFont(font);\n    emit fontChanged();\n}\n\nQFont TextCharFormat::font() const\n{\n    return QTextCharFormat::font();\n}\n\nQVariant TextCharFormat::foreground() const\n{\n    return QTextCharFormat::foreground().color();\n}\n\nvoid TextCharFormat::setForeground( const QVariant& foreground )\n{\n    if ( foreground.canConvert<QColor>() )\n    {\n        QTextCharFormat::setForeground( QBrush( foreground.value< QColor >() ) );\n        emit foregroundChanged();\n    }\n}\n"
  },
  {
    "path": "src/modules/value-editor/textcharformat.h",
    "content": "#pragma once\n#include <QObject>\n#include <QTextCharFormat>\n\n/***\n * Based on https://github.com/stephenquan/QtSyntaxHighlighterApp\n *\n * Original code is licensed under the Apache License, Version 2.0\n * (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n * */\n\nclass TextCharFormat : public QObject, public QTextCharFormat\n{\n    Q_OBJECT\n    Q_PROPERTY (QFont font READ font WRITE setFont NOTIFY fontChanged)\n    Q_PROPERTY (QVariant foreground READ foreground WRITE setForeground NOTIFY foregroundChanged)\n\npublic:\n    TextCharFormat(QObject* parent = nullptr);\n\nsignals:\n    void fontChanged();\n    void foregroundChanged();\n\nprotected:\n    void setFont(const QFont& font);\n    QFont font() const;\n\n    QVariant foreground() const;\n    void setForeground(const QVariant& foreground);\n};\n"
  },
  {
    "path": "src/modules/value-editor/valueviewmodel.cpp",
    "content": "#include \"valueviewmodel.h\"\n#include <qredisclient/utils/text.h>\n#include <QCoreApplication>\n#include <QDebug>\n#include <QQmlEngine>\n#include <QSettings>\n\nValueEditor::ValueViewModel::ValueViewModel(const QString& loadingTitle)\n    : BaseListModel(),\n      m_model(nullptr),\n      m_connection(nullptr),\n      m_startFramePosition(0),\n      m_lastLoadedRowFrameSize(0),\n      m_singlePageMode(false),\n      m_tabTitle(loadingTitle)\n{}\n\nint ValueEditor::ValueViewModel::rowCount(const QModelIndex& parent) const {\n  Q_UNUSED(parent);\n\n  if (!m_model) return 0;\n\n  if (m_singlePageMode) {\n    return m_model->rowsCount();\n  } else {\n    return m_lastLoadedRowFrameSize;\n  }\n}\n\nint ValueEditor::ValueViewModel::columnCount(const QModelIndex& parent) const {\n    Q_UNUSED(parent);\n\n    if (!m_model) return 0;\n\n    return m_model->getColumnNames().size();\n}\n\nQString ValueEditor::ValueViewModel::tabLoadingTitle() const {\n    return m_tabTitle;\n}\n\nvoid ValueEditor::ValueViewModel::setTabError(const QString &t)\n{\n    m_tabTitle = t;\n}\n\nbool ValueEditor::ValueViewModel::isModelLoaded() const {\n    return !m_model.isNull();\n}\n\nQVariant ValueEditor::ValueViewModel::data(const QModelIndex& index,\n                                           int role) const {\n  if (!isIndexValid(index)) return QVariant();\n\n  int mappedRole = role;\n\n  if (role == Qt::DisplayRole && index.column() > 0) {\n      mappedRole = m_model->getRoles().key(m_model->getColumnNames().at(index.column()).toLatin1());\n  }\n\n  return m_model->getData(m_startFramePosition + index.row(), mappedRole);\n}\n\nQHash<int, QByteArray> ValueEditor::ValueViewModel::roleNames() const {\n  auto roles = m_model->getRoles();\n  roles.insert(Qt::DisplayRole, \"display\");\n  return roles;\n}\n\nQSharedPointer<ValueEditor::Model> ValueEditor::ValueViewModel::model() {\n  return m_model;\n}\n\nvoid ValueEditor::ValueViewModel::setModel(QSharedPointer<Model> model) {\n  m_model = model;\n  emit modelLoaded();\n}\n\nvoid ValueEditor::ValueViewModel::setConnection(QSharedPointer<RedisClient::Connection> c)\n{\n    m_connection = c;\n}\n\nvoid ValueEditor::ValueViewModel::renameKey(const QString& newKeyName) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  m_model->setKeyName(printableStringToBinary(newKeyName),\n                      [this](const QString& err) {\n                        if (err.size() > 0) {\n                          emit error(err);\n                          return;\n                        }\n\n                        emit keyRenamed();\n                      });\n}\n\nvoid ValueEditor::ValueViewModel::setTTL(const QString& newTTL) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  m_model->setTTL(newTTL.toLong(), [this](const QString& err) {\n    if (err.size() > 0) {\n      emit error(err);\n      return;\n    }\n\n    emit keyTTLChanged();\n  });\n}\n\nvoid ValueEditor::ValueViewModel::persistKey() {\n    if (!m_model) {\n      qWarning() << \"Model is not loaded\";\n      return;\n    }\n\n    m_model->persistKey([this](const QString& err) {\n      if (err.size() > 0) {\n        emit error(err);\n        return;\n      }\n\n      emit keyTTLChanged();\n    });\n}\n\nvoid ValueEditor::ValueViewModel::removeKey() {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  m_model->removeKey([this](const QString& err) {\n    if (err.size() > 0) {\n      emit error(err);\n      return;\n    }\n\n    emit keyRemoved();\n  });\n}\n\nvoid ValueEditor::ValueViewModel::close()\n{\n    emit tabClosed();\n}\n\nQVariantList ValueEditor::ValueViewModel::columnNames() {\n  QVariantList result;\n\n  if (!m_model) return result;\n\n  foreach (QString str, m_model->getColumnNames()) {\n    result.append(QVariant(str));\n  }\n\n  return result;\n}\n\nvoid ValueEditor::ValueViewModel::reload() {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  m_model->clearRowCache();\n  m_model->loadRowsCount([this](const QString& err) {\n    if (err.size() > 0 || m_model->rowsCount() <= 0) {\n      emit error(\n          QCoreApplication::translate(\"RESP\", \"Cannot reload key value: %1\")\n              .arg(err));\n      return;\n    }\n\n    emit totalRowCountChanged();\n    emit pageSizeChanged();\n\n    loadRows(m_startFramePosition, m_model->rowsCount() < pageSize()\n                                       ? m_model->rowsCount()\n                                       : pageSize());\n  });\n}\n\nvoid ValueEditor::ValueViewModel::setSinglePageMode(bool v) {\n  m_singlePageMode = v;\n  emit singlePageModeChanged();\n}\n\nbool ValueEditor::ValueViewModel::singlePageMode() const {\n  return m_singlePageMode;\n}\n\nbool ValueEditor::ValueViewModel::isRowLoaded(int i) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return false;\n  }\n\n  return m_model->isRowLoaded(i);\n}\n\nvoid ValueEditor::ValueViewModel::loadRows(int start, int limit) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  int rowsLeft = totalRowCount() - start;\n  int loaded = (rowsLeft > limit) ? limit : rowsLeft;\n\n  // frame already loaded\n  if (m_model->isRowLoaded(start) && m_model->isRowLoaded(start + loaded - 1)) {\n    m_startFramePosition = start;\n    m_lastLoadedRowFrameSize = loaded;\n\n    emit layoutAboutToBeChanged();\n    emit rowsLoaded(start, loaded);\n    emit layoutChanged();\n    return;\n  }\n\n  QString msg = QCoreApplication::translate(\"RESP\", \"Cannot load key value: %1\");\n\n  m_model->loadRows(\n      start, limit,\n      [this, start, limit, msg](const QString& err, unsigned long rowsCount) {\n        if (!err.isEmpty()) {\n          emit error(msg.arg(err));\n          return;\n        }\n\n        m_lastLoadedRowFrameSize = rowsCount > limit ? limit : rowsCount;\n        m_startFramePosition = start;\n\n        emit layoutAboutToBeChanged();\n        emit rowsLoaded(start, m_lastLoadedRowFrameSize);\n        emit layoutChanged();\n      });\n}\n\nvoid ValueEditor::ValueViewModel::addRow(const QVariantMap& row) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  m_model->addRow(row, [this](const QString& err) {\n    if (err.size() > 0) {\n      emit error(err);\n      return;\n    }\n    emit layoutChanged();\n  });\n}\n\nvoid ValueEditor::ValueViewModel::updateRow(int rowIndex, const QVariantMap& row) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  if (rowIndex < 0 || !m_model->isRowLoaded(rowIndex)) return;\n\n  m_model->updateRow(rowIndex, row, [this, rowIndex](const QString& err) {\n    if (err.size() > 0) {\n      emit error(err);\n      return;\n    }\n    emit dataChanged(index(rowIndex, 0), index(rowIndex, m_model->getColumnNames().size() - 1));\n    emit valueUpdated();\n  });\n}\n\nvoid ValueEditor::ValueViewModel::deleteRow(int rowIndex) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  if (rowIndex < 0 || !m_model->isRowLoaded(rowIndex)) return;\n\n  m_model->removeRow(rowIndex, [this, rowIndex](const QString& err) {\n    if (err.size() > 0) {\n      emit error(err);\n      return;\n    }\n\n    emit beginRemoveRows(QModelIndex(), rowIndex, rowIndex);\n    emit endRemoveRows();\n\n    if (m_lastLoadedRowFrameSize > 0)\n        m_lastLoadedRowFrameSize -= 1;\n\n    if (m_model->rowsCount() == 0) emit keyRemoved();\n  });\n}\n\nint ValueEditor::ValueViewModel::totalRowCount() {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return 0;\n  }\n\n  return m_model->rowsCount();\n}\n\nint ValueEditor::ValueViewModel::pageSize() {\n  QSettings settings;\n\n  return settings.value(\"app/valueEditorPageSize\", 100).toInt();\n}\n\nQVariantMap ValueEditor::ValueViewModel::getRow(int rowIndex) {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return QVariantMap();\n  }\n\n  if (rowIndex < 0 || !m_model->isRowLoaded(rowIndex)) return QVariantMap();\n\n  QHash<int,QByteArray> names = roleNames();\n  QHashIterator<int, QByteArray> i(names);\n  QVariantMap res;\n\n  while (i.hasNext()) {\n      i.next();\n\n      if (i.value() == \"display\")\n          continue;\n\n      QVariant d = m_model->getData(rowIndex, i.key());\n      res[i.value()] = d;\n  }\n\n  return res;\n}\n\nvoid ValueEditor::ValueViewModel::loadRowsCount() {\n  if (!m_model) {\n    qWarning() << \"Model is not loaded\";\n    return;\n  }\n\n  m_model->loadRowsCount([this](const QString& err) {\n    if (err.size() > 0) {\n      emit error(err);\n      return;\n    }\n\n    emit totalRowCountChanged();\n  });\n}\n\nQVariant ValueEditor::ValueViewModel::filter(const QString& key) const\n{\n    if (!m_model) {\n      qWarning() << \"Model is not loaded\";\n      return QVariant();\n    }\n\n    return m_model->filter(key);\n}\n\nvoid ValueEditor::ValueViewModel::setFilter(const QString& key, QVariant v)\n{\n    if (!m_model) {\n      qWarning() << \"Model is not loaded\";\n      return;\n    }\n\n    return m_model->setFilter(key, v);\n}\n"
  },
  {
    "path": "src/modules/value-editor/valueviewmodel.h",
    "content": "#pragma once\n#include <QAbstractListModel>\n#include <QJSValue>\n#include <QSharedPointer>\n#include <QVariantMap>\n#include \"common/baselistmodel.h\"\n#include \"keymodel.h\"\n\nnamespace ValueEditor {\n\nclass ValueViewModel : public BaseListModel {\n  Q_OBJECT\n\n  Q_PROPERTY(bool isLoaded READ isModelLoaded NOTIFY modelLoaded)\n  Q_PROPERTY(bool singlePageMode READ singlePageMode WRITE setSinglePageMode NOTIFY singlePageModeChanged)\n  Q_PROPERTY(int totalRowCount READ totalRowCount NOTIFY totalRowCountChanged)\n  Q_PROPERTY(int pageSize READ pageSize NOTIFY pageSizeChanged)\n  Q_PROPERTY(\n      QVariantList columnNames READ columnNames NOTIFY columnNamesChanged)\n\n public:\n  ValueViewModel(const QString& loadingTitle);\n  ~ValueViewModel() override {}\n\n  int rowCount(const QModelIndex& parent = QModelIndex()) const override;\n\n  int columnCount(const QModelIndex& parent = QModelIndex()) const override;\n\n  QVariant data(const QModelIndex& index, int role) const override;\n  QHash<int, QByteArray> roleNames() const override;\n\n  QSharedPointer<Model> model();\n  void setModel(QSharedPointer<Model> model);\n\n  void setConnection(QSharedPointer<RedisClient::Connection> c);\n\n  QString tabLoadingTitle() const;\n\n  void setTabError(const QString& t);\n\n  void close();\n\n public:\n  // general key operations\n  Q_INVOKABLE void renameKey(const QString& newKeyName);\n  Q_INVOKABLE void setTTL(const QString& newTTL);\n  Q_INVOKABLE void persistKey();\n  Q_INVOKABLE void removeKey();\n\n  // single row operations\n  Q_INVOKABLE bool isRowLoaded(int i);\n  Q_INVOKABLE void addRow(const QVariantMap& row);\n  Q_INVOKABLE void updateRow(int i, const QVariantMap& row);\n  Q_INVOKABLE void deleteRow(int i);\n  Q_INVOKABLE QVariantMap getRow(int i);\n\n  // multi row operations\n  Q_INVOKABLE void loadRowsCount();\n  Q_INVOKABLE void loadRows(int start, int limit);\n  Q_INVOKABLE void reload();\n\n  // filters\n  Q_INVOKABLE QVariant filter(const QString& key) const;\n  Q_INVOKABLE void setFilter(const QString&, QVariant);\n\n  void setSinglePageMode(bool v);\n  bool singlePageMode() const;\n\n  bool isModelLoaded() const;\n\n  int totalRowCount();\n  int pageSize();\n  QVariantList columnNames();\n\n signals:\n  void rowsLoaded(int start, int count);\n  void error(QString error);\n  void totalRowCountChanged();\n  void pageSizeChanged();\n  void columnNamesChanged();\n  void keyRenamed();\n  void keyRemoved();\n  void keyTTLChanged();\n  void singlePageModeChanged();\n  void modelLoaded();\n  void tabClosed();\n  void valueUpdated();\n\n private:\n  QSharedPointer<Model> m_model;\n  QSharedPointer<RedisClient::Connection> m_connection;\n  int m_startFramePosition;\n  int m_lastLoadedRowFrameSize;\n  bool m_singlePageMode;\n  QString m_tabTitle;\n};\n\n}  // namespace ValueEditor\n"
  },
  {
    "path": "src/py/formatters/__init__.py",
    "content": "from .binary import BinaryFormatter\nfrom .cbor import CBORFormatter\nfrom .msgpack import MsgpackFormatter\nfrom .phpserialize import PhpSerializeFormatter\n\ntry:\n    from .pickle import PickleFormatter\n    pickle_formatter_loaded = True\nexcept Exception:\n    pickle_formatter_loaded = False\n\nENABLED_FORMATTERS = {\n    \"binary\": BinaryFormatter(),\n    \"cbor\": CBORFormatter(),\n    \"msgpack\": MsgpackFormatter(),\n    \"php\": PhpSerializeFormatter(),\n}\n\n# NOTE(u_glide): Numpy doesn't work on Windows 20.04\n# For more info and progress on this issue see\n# https://github.com/numpy/numpy/issues/16744\nif pickle_formatter_loaded:\n    ENABLED_FORMATTERS[\"pickle\"] = PickleFormatter()\n\n\ndef get_formatters_list():\n    return [(name, f.read_only)\n            for name, f in ENABLED_FORMATTERS.items()]\n\n\ndef decode(name, value):\n    formatter = ENABLED_FORMATTERS[name]\n\n    error = \"\"\n    read_only = formatter.read_only\n    decode_format = formatter.decode_format\n\n    try:\n        result = formatter.decode(value)\n\n        if type(result) is dict:\n            result_dict = result\n            result = result_dict.get('output', '')\n            error = result_dict.get('error', error)\n            read_only = result_dict.get('read-only', read_only)\n            decode_format = result_dict.get('decode-format', decode_format)\n\n    except Exception as e:\n        read_only = True\n        error = (\n            \"Embedded formatter %s error: %s (value: %s)\"\n            % (name, str(e), value)\n        )\n        result = \"\"\n\n    return [error, result, read_only, decode_format]\n\n\ndef validate(name, value):\n    return ENABLED_FORMATTERS[name].validate(value)\n\n\ndef encode(name, value):\n    formatter = ENABLED_FORMATTERS[name]\n\n    if formatter.read_only:\n        return [\"Formatter %s doesn't support encoding\" % name]\n\n    error = \"\"\n\n    try:\n        result = formatter.encode(value)\n    except Exception as e:\n        error = (\n                \"Embedded formatter %s error: %s (value: %s)\"\n                % (name, str(e), value)\n        )\n        result = \"\"\n\n    return [error, result]\n"
  },
  {
    "path": "src/py/formatters/base.py",
    "content": "class BaseFormatter(object):\n\n    read_only = True\n\n    decode_format = \"plain_text\"\n\n    def decode(self, value):\n        raise NotImplementedError()\n\n    def encode(self, value):\n        raise NotImplementedError()\n\n    def validate(self, value):\n        try:\n            result = self.decode(value)\n\n            if type(result) is dict:\n                err = result.get('error', '')\n                return [err == '', err]\n            else:\n                return [True, \"\"]\n        except Exception as e:\n            return [False, str(e)]\n"
  },
  {
    "path": "src/py/formatters/binary.py",
    "content": "import bitstring\n\nfrom .base import BaseFormatter\n\n\nclass BinaryFormatter(BaseFormatter):\n\n    def decode(self, value):\n        return bitstring.BitArray(value).bin\n"
  },
  {
    "path": "src/py/formatters/cbor.py",
    "content": "import cbor\nimport json\nfrom .base import BaseFormatter\n\n\nclass CBORFormatter(BaseFormatter):\n\n    decode_format = \"json\"\n\n    def decode(self, value):\n        return json.dumps(cbor.loads(value), ensure_ascii=False)\n"
  },
  {
    "path": "src/py/formatters/msgpack.py",
    "content": "import base64\nimport io\nimport json\n\nimport msgpack\n\nfrom .base import BaseFormatter\n\n\nclass MsgpackFormatter(BaseFormatter):\n\n    read_only = False\n\n    decode_format = \"json\"\n\n    def decode(self, value):\n        read_only = self.read_only\n        unpacked = ''\n        error = ''\n\n        try:\n            unpacked = msgpack.loads(value, raw=False, strict_map_key=False)\n        except msgpack.ExtraData as e:\n            read_only = True\n\n            buf = io.BytesIO(value)\n            unpacker = msgpack.Unpacker(buf, raw=False, strict_map_key=False)\n            for data in unpacker:\n                unpacked = data\n                error = ('First object from the stream is shown, value was '\n                         'truncated by {extra_len} bytes.'\n                         .format(extra_len=len(e.extra)))\n                break\n\n        return {\n            'output': json.dumps(unpacked,\n                                 default=self.default,\n                                 ensure_ascii=False),\n            'read-only': read_only,\n            'error': error\n        }\n\n    def encode(self, value):\n        return msgpack.dumps(json.loads(value))\n\n    @staticmethod\n    def default(o):\n        if isinstance(o, msgpack.Timestamp):\n            return o.to_datetime().isoformat()\n\n        elif isinstance(o, bytes):\n            try:\n                return o.decode(\"utf-8\")\n            except UnicodeDecodeError:\n                return base64.b64encode(o)\n        else:\n            return str(o)\n"
  },
  {
    "path": "src/py/formatters/phpserialize.py",
    "content": "import phpserialize\nimport json\n\nfrom .base import BaseFormatter\n\n\nclass PhpSerializeFormatter(BaseFormatter):\n\n    read_only = False\n\n    decode_format = \"json\"\n\n    def decode(self, value):\n        read_only = self.read_only\n        deserialized = ''\n        error = ''\n\n        try:\n            deserialized = phpserialize.loads(\n                value, decode_strings=True, object_hook=phpserialize.phpobject)\n\n        except ValueError as e:\n            read_only = True\n            error = 'Value cannot be unserialized: {} (value: {})'.format(\n                e, value)\n\n        return {\n            'output': json.dumps(deserialized, ensure_ascii=False,\n                                 default=self.default),\n            'read-only': read_only,\n            'error': error\n        }\n\n    def encode(self, value):\n        return phpserialize.dumps(json.loads(value))\n\n    @staticmethod\n    def default(o):\n        if isinstance(o, phpserialize.phpobject):\n            return o._asdict()\n"
  },
  {
    "path": "src/py/formatters/pickle.py",
    "content": "import json\nimport pickle\n\ntry:\n    import numpy as np\n    import pandas as pd\n    numpy_support = True\nexcept ImportError:\n    numpy_support = False\n\nfrom .base import BaseFormatter\n\n\nclass PickleFormatter(BaseFormatter):\n\n    decode_format = \"json\"\n\n    def decode(self, value):\n        def get_json_output(deserialized_object):\n            return json.dumps(deserialized_object,\n                              default=self.default,\n                              ensure_ascii=False)\n\n        read_only = self.read_only\n        decode_format = self.decode_format\n        deserialized = ''\n        output = ''\n        error = ''\n\n        try:\n            deserialized = pickle.loads(value)\n        except pickle.UnpicklingError as e:\n            read_only = True\n            error = 'Value cannot be deserialized with pickle: {} ' \\\n                    '(value: {})'.format(e, value)\n\n        if numpy_support:\n            if isinstance(deserialized, pd.Series):\n                output = f'{str(type(deserialized))[1:-1]}\\n' \\\n                         f'{deserialized.to_string()}'\n                decode_format = 'plain_text'\n            elif isinstance(deserialized, pd.DataFrame):\n                html = deserialized.to_html(render_links=True, border=0)\n                output = self.format_html_output(deserialized, html)\n                decode_format = 'html'\n            elif isinstance(deserialized, np.ndarray):\n                html = pd.DataFrame(deserialized).to_html(render_links=True,\n                                                          border=0)\n                output = self.format_html_output(deserialized, html)\n                decode_format = 'html'\n            else:\n                output = get_json_output(deserialized)\n        else:\n            output = get_json_output(deserialized)\n\n        return {\n            'output': output,\n            'decode-format': decode_format,\n            'read-only': read_only,\n            'error': error\n        }\n\n    @staticmethod\n    def default(o):\n        if numpy_support:\n            if isinstance(o, pd.Timestamp):\n                return o.isoformat()\n            if isinstance(o, np.ndarray):\n                return o.tolist()\n            if isinstance(o, pd.Series):\n                return o.to_json()\n            if isinstance(o, pd.DataFrame):\n                return json.loads(o.to_json(orient='index', date_format='iso'))\n            else:\n                return str(o)\n        else:\n            return str(o)\n\n    @staticmethod\n    def format_html_output(data, html):\n        style = '<style type=\"text/css\">' \\\n                'th, td { padding: 5px 15px 5px 0px; ' \\\n                'text-align: left; }</style>'\n        return '{}<p><b>{}</b></p>{}'.format(style, str(type(data))[1:-1], html)\n"
  },
  {
    "path": "src/py/py.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/python\">\n        <file>formatters/__init__.py</file>\n        <file>formatters/base.py</file>\n        <file>formatters/binary.py</file>\n        <file>formatters/cbor.py</file>\n        <file>formatters/msgpack.py</file>\n        <file>formatters/pickle.py</file>\n        <file>formatters/phpserialize.py</file>\n        <file>rdb/__init__.py</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "src/py/rdb/__init__.py",
    "content": "import calendar\n\nimport rdbtools\nfrom rdbtools.encodehelpers import STRING_ESCAPE_UTF8, STRING_ESCAPE_RAW\n\nVALID_TYPES = (\"hash\", \"set\", \"string\", \"list\", \"sortedset\")\n\n\ndef process_command(callback, path_to_rdb, db,\n                    include_keys_pattern,\n                    exclude_keys_pattern,\n                    key_types, scan_keys=False):\n    filters = {}\n\n    if db:\n        filters['dbs'] = [int(db)]\n\n    if include_keys_pattern:\n        filters['keys'] = include_keys_pattern\n\n    if exclude_keys_pattern:\n        filters['not_keys'] = exclude_keys_pattern\n\n    if key_types:\n        filters['types'] = []\n        for x in key_types:\n            if not x in VALID_TYPES:\n                raise ValueError(\n                    'Invalid type provided - %s. '\n                    'Expected one of %s' % (x, (\", \".join(VALID_TYPES)))\n                )\n            else:\n                filters['types'].append(x)\n\n    parser = rdbtools.RdbParser(callback=callback, filters=filters, ignore_values=scan_keys)\n    parser.parse(path_to_rdb)\n\n\ndef rdb_list_keys(path_to_rdb, db,\n                  include_keys_pattern=None,\n                  exclude_keys_pattern=None,\n                  key_types=None):\n    class KeysOnlyCallback(rdbtools.RdbCallback):\n        def __init__(self, string_escape=None):\n            super(KeysOnlyCallback, self).__init__(string_escape)\n            self._out = set()\n\n        def key(self, key):\n            self._out.add(self.encode_key(key))\n\n        def keys(self):\n            return list(self._out)\n\n    callback = KeysOnlyCallback(string_escape=STRING_ESCAPE_UTF8)\n\n    process_command(callback, path_to_rdb, db,\n                    include_keys_pattern,\n                    exclude_keys_pattern,\n                    key_types)\n\n    return callback.keys()\n\n\ndef rdb_export_as_commands(path_to_rdb, db,\n                           include_keys_pattern=None,\n                           exclude_keys_pattern=None,\n                           key_types=None):\n    def _unix_timestamp(dt):\n        return calendar.timegm(dt.utctimetuple())\n\n    class CommandsCallback(rdbtools.RdbCallback):\n        def __init__(self, string_escape=None):\n            super(CommandsCallback, self).__init__(string_escape)\n            self._commands = []\n            self.reset()\n\n        def reset(self):\n            self._expires = {}\n\n        def set_expiry(self, key, dt):\n            self._expires[key] = dt\n\n        def get_expiry_seconds(self, key):\n            if key in self._expires:\n                return _unix_timestamp(self._expires[key])\n            return None\n\n        def expires(self, key):\n            return key in self._expires\n\n        def pre_expiry(self, key, expiry):\n            if expiry is not None:\n                self.set_expiry(key, expiry)\n\n        def post_expiry(self, key):\n            if self.expires(key):\n                self.expireat(key, self.get_expiry_seconds(key))\n\n        def emit(self, *args):\n            self._commands.append(args)\n\n        def start_database(self, db_number):\n            self.reset()\n            self.select(db_number)\n\n        # String handling\n\n        def set(self, key, value, expiry, info):\n            self.pre_expiry(key, expiry)\n            self.emit(b'SET', key, value)\n            self.post_expiry(key)\n\n        # Hash handling\n\n        def start_hash(self, key, length, expiry, info):\n            self.pre_expiry(key, expiry)\n\n        def hset(self, key, field, value):\n            self.emit(b'HSET', key, field, value)\n\n        def end_hash(self, key):\n            self.post_expiry(key)\n\n        # Set handling\n\n        def start_set(self, key, cardinality, expiry, info):\n            self.pre_expiry(key, expiry)\n\n        def sadd(self, key, member):\n            self.emit(b'SADD', key, member)\n\n        def end_set(self, key):\n            self.post_expiry(key)\n\n        # List handling\n\n        def start_list(self, key, expiry, info):\n            self.pre_expiry(key, expiry)\n\n        def rpush(self, key, value):\n            self.emit(b'RPUSH', key, value)\n\n        def end_list(self, key, info):\n            self.post_expiry(key)\n\n        # Sorted set handling\n\n        def start_sorted_set(self, key, length, expiry, info):\n            self.pre_expiry(key, expiry)\n\n        def zadd(self, key, score, member):\n            self.emit(b'ZADD', key, score, member)\n\n        def end_sorted_set(self, key):\n            self.post_expiry(key)\n\n        # streams and modules, not currently supported\n\n        def start_stream(self, key, listpacks_count, expiry, info):\n            # TODO send RESTORE command\n            pass\n\n        def start_module(self, key, module_name, expiry, info):\n            # TODO send RESTORE command\n            return False\n\n        # Other misc commands\n\n        def select(self, db_number):\n            self.emit(b'SELECT', db_number)\n\n        def expireat(self, key, timestamp):\n            self.emit(b'EXPIREAT', key, timestamp)\n\n        def commands(self):\n            return self._commands\n\n    callback = CommandsCallback(string_escape=STRING_ESCAPE_RAW)\n\n    process_command(callback, path_to_rdb, db,\n                    include_keys_pattern,\n                    exclude_keys_pattern,\n                    key_types)\n\n    return callback.commands()\n\n"
  },
  {
    "path": "src/py/requirements.txt",
    "content": "bitstring\ncbor\nmsgpack\ngit+https://github.com/mrnom/phpserialize.git#egg=phpserialize\ngit+https://github.com/uglide/redis-rdb-tools#egg=rdbtools\npython-lzf\n"
  },
  {
    "path": "src/qml/AppToolBar.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.15\nimport QtQuick.Controls.Styles 1.1\nimport Qt.labs.platform 1.1\nimport QtQml.Models 2.2\nimport \".\"\nimport \"./common\"\nimport \"./common/platformutils.js\" as PlatformUtils\n\nToolBar {\n\n    background: Rectangle {\n        implicitHeight: 40\n        color: sysPalette.button\n    }\n\n    RowLayout {\n        anchors.fill: parent\n        spacing: 0\n\n        RowLayout {\n            Layout.maximumWidth: connectionsTree.width + 1\n\n            BetterButton {\n                Layout.fillWidth: true\n                Layout.minimumWidth: 190\n                iconSource: PlatformUtils.getThemeIcon(\"add.svg\")\n                text: qsTranslate(\"RESP\",\"Connect to Redis Server\")\n                objectName: \"rdm_connect_to_redis_server_btn\"\n\n                onClicked: {\n                    connectionSettingsDialog.settings = connectionsManager.createEmptyConfig()\n                    connectionSettingsDialog.open()\n                }\n            }\n\n\n            ImageButton {\n                id: connectionsMenuBtn\n                objectName: \"rdm_connections_menu_btn\"\n\n                Layout.preferredWidth: 30\n                iconSource: PlatformUtils.getThemeIcon(\"list.svg\")\n\n                onClicked: menu.open()\n\n                FileDialog {\n                    id: importConnectionsDialog\n                    title: qsTranslate(\"RESP\",\"Import Connections\")\n                    nameFilters: [\"Connections (*.json)\"]\n                    fileMode: FileDialog.OpenFile\n                    onAccepted: connectionsManager.importConnections(qmlUtils.getPathFromUrl(file))\n                }\n\n                FileDialog {\n                    id: exportConnectionsDialog\n                    title: qsTranslate(\"RESP\",\"Export Connections\")\n                    nameFilters: [\"Connections (*.json)\"]\n                    fileMode: FileDialog.SaveFile\n                    onAccepted: connectionsManager.saveConnectionsConfigToFile(qmlUtils.getPathFromUrl(file))\n                }\n\n                BetterMenu {\n                    id: menu\n\n                    BetterMenuItem {\n                        objectName: \"rdm_import_connections_btn\"\n                        text: qsTranslate(\"RESP\",\"Import Connections\")\n                        onTriggered: importConnectionsDialog.open()\n                    }\n                    BetterMenuItem {\n                        objectName: \"rdm_export_connections_btn\"\n                        text: qsTranslate(\"RESP\",\"Export Connections\")\n                        onTriggered: exportConnectionsDialog.open()\n                    }\n                }\n            }\n\n            ImageButton {\n                id: toggleTreeViewBtn\n                Layout.preferredWidth: 30\n                iconSource: PlatformUtils.getThemeIcon(\"square-half.svg\")\n                imgWidth: 15\n                imgHeight: 15\n\n                onClicked: {\n                    connectionsTreeWrapper.visible = !connectionsTreeWrapper.visible\n                }\n            }\n        }\n\n        Rectangle { width: 1; color: sysPalette.mid; Layout.fillHeight: true;}\n\n        Item { Layout.fillWidth: true }\n\n        BetterButton {\n            implicitWidth: 40\n            iconSource: PlatformUtils.getThemeIcon(\"alert.svg\")\n            tooltip: qsTranslate(\"RESP\",\"Report issue\")\n            onClicked: Qt.openUrlExternally(\"https://github.com/uglide/RedisDesktopManager/issues\")\n        }\n\n        BetterButton {\n            implicitWidth: 40\n            iconSource: PlatformUtils.getThemeIcon(\"help.svg\")\n            tooltip: qsTranslate(\"RESP\",\"Documentation\")\n            onClicked: Qt.openUrlExternally(\"http://docs.resp.app/en/latest/\")\n        }\n\n        BetterButton {\n            implicitWidth: 40\n            iconSource: PlatformUtils.getThemeIcon(\"telegram.svg\")\n            tooltip: qsTranslate(\"RESP\",\"Join Telegram Chat\")\n            onClicked: Qt.openUrlExternally(\"https://t.me/RedisDesktopManager\")\n        }\n\n        BetterButton {\n            implicitWidth: 40\n            iconSource: PlatformUtils.getThemeIcon(\"twi.svg\")\n            tooltip: qsTranslate(\"RESP\",\"Follow\")\n            onClicked: Qt.openUrlExternally(\"https://twitter.com/dev_rdm\")\n        }\n\n        BetterButton {\n            implicitWidth: 40\n            iconSource: PlatformUtils.getThemeIcon(\"github.svg\")\n            tooltip: qsTranslate(\"RESP\",\"Star on GitHub!\")\n            onClicked: Qt.openUrlExternally(\"https://github.com/uglide/RedisDesktopManager\")\n        }\n\n        Item { Layout.fillWidth: true }\n\n        BetterButton {\n            iconSource: PlatformUtils.getThemeIcon(\"log.svg\")\n            text: qsTranslate(\"RESP\",\"Log\")\n\n            onClicked: logDrawer.open()\n        }\n\n        BetterButton {\n            objectName: \"rdm_extension_server_settings_btn\"\n            iconSource: PlatformUtils.getThemeIcon(\"server_2.svg\")\n            text: qsTranslate(\"RESP\",\"Extension Server\")\n\n            onClicked: {\n                extServerSettingsDialog.item.open()\n            }\n        }\n\n        BetterButton {\n            objectName: \"rdm_global_settings_btn\"\n            iconSource: PlatformUtils.getThemeIcon(\"settings.svg\")\n            text: qsTranslate(\"RESP\",\"Settings\")\n\n            onClicked: {\n                settingsDialog.item.open()\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/qml/LogView.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.13\nimport \"./common\"\n\nFastTextView {\n    id: root\n\n    property alias eventsModel: modelConnections.target\n\n    model: ListModel {}\n\n    function dumpText() {\n        var allStrings = \"\";\n        for (var ind=0; ind < root.model.count; ind++) {\n            allStrings += root.model.get(ind)[\"msg\"] + \"\\n\"\n        }\n        return allStrings\n    }\n\n    color: sysPalette.base\n    border.color: sysPalette.shadow\n    border.width: 1\n    showLineNumbers: false\n\n    Connections {\n        id: modelConnections\n        function onLog(msg) {\n            if (model.count > 1500) {\n                model.remove(0, model.count - 1000)\n            }\n\n            model.append({\"msg\": msg})\n            positionViewAtEnd()\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/QuickStartDialog.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Window 2.3\nimport \"./common\"\nimport \"./common/platformutils.js\" as PlatformUtils\n\nBetterDialog {\n    id: root\n    objectName: \"rdm_quick_start_dialog\"\n    title: qsTranslate(\"RESP\",\"Getting Started\")\n\n    footer: null\n\n    contentItem: Rectangle {\n        id: rootItem\n        color: sysPalette.base\n        anchors.fill: parent\n        implicitWidth:  750\n        implicitHeight: 150\n\n        Control {\n            palette: approot.palette\n            anchors.fill: parent\n            anchors.margins: 30\n\n            ColumnLayout {\n                anchors.fill: parent\n\n                Item { Layout.fillHeight: true }\n\n                RowLayout {\n                    id: msgLayout\n\n                    Layout.fillHeight: true\n                    Layout.alignment: Qt.AlignHCenter\n\n                    BetterLabel {\n                        Layout.fillWidth: true\n                        wrapMode: Text.WordWrap\n                        horizontalAlignment: Text.AlignHCenter\n                        text: qsTranslate(\"RESP\",\"Thank you for choosing RESP.app. Let's make your Redis experience better.\")\n                        font.pixelSize: 16\n\n                        Component.onCompleted: {\n                            if (!PlatformUtils.isOSX()) {\n                                root.width = contentWidth + 100\n                            }\n                        }\n                    }\n                }\n\n                Item { Layout.fillHeight: true }\n\n                RowLayout {\n\n                    Item { Layout.fillWidth: true }\n\n                    BetterButton {\n                        text: qsTranslate(\"RESP\",\"Connect to Redis-Server\")\n                        palette.button: \"#c6302b\"\n                        palette.buttonText: \"#ffffff\"\n                        onClicked: {\n                            root.close()\n                            connectionSettingsDialog.settings = connectionsManager.createEmptyConfig()\n                            connectionSettingsDialog.open()\n                        }\n                    }\n\n                    BetterButton {\n                        property string url: \"http://docs.resp.app/en/latest/quick-start/\"\n\n                        text: qsTranslate(\"RESP\",\"Read the Docs\")\n                        tooltip: url\n\n                        onClicked: Qt.openUrlExternally(url)\n                    }\n\n                    Item { Layout.fillWidth: true }\n                }\n\n                Item { Layout.fillHeight: true }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/WelcomeTab.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.3\nimport \"./common\"\nimport \"./common/platformutils.js\" as PlatformUtils\n\nBetterTab {\n    id: root\n    ColumnLayout {\n        anchors.centerIn: parent\n        width: Math.min(parent.width * 0.9, 600)\n\n        RowLayout {\n            id: topLayout\n            spacing: 15\n            Layout.fillWidth: true\n            Layout.preferredHeight: 350\n\n            Image {\n                id: logo\n                source: \"qrc:/images/redisinsight.svg\"\n                Layout.preferredWidth: 50\n                Layout.preferredHeight: 50\n                Layout.alignment: Qt.AlignTop\n                fillMode: Image.PreserveAspectFit\n            }\n\n            ColumnLayout {\n                Layout.fillWidth: true\n                RichTextWithLinks { Layout.fillWidth: true; html: '<span style=\"font-size:26px;\"><b>Redis</b>Insight is the successor to <span style=\"color: grey;\">RESP.app</span></span>'}\n                RichTextWithLinks { Layout.fillWidth: true; html: '<div style=\"font-size:14px; line-height: 120%\">In 2022, <a href=\"https://redis.com/blog/respapp-joining-redis/\">Redis joined forces with the creator of RESP.app, Igor Malinovskyi</a>, '\n                                                                  + 'bringing RESP.app’s popular features into RedisInsight. RedisInsight now provides improved '\n                                                                  + 'performance and these additional features:</div>'\n                                                                  + '<ul style=\"font-size:14px;\">'\n                                                                  + '<li style=\"line-height: 150%\">SSH tunneling support</li>'\n                                                                  + '<li style=\"line-height: 150%\">Support for <a href=\"https://redis.io/docs/stack/about/\">RedisStack</a></li>'\n                                                                  + '<li style=\"line-height: 150%\">Database analysis and performance improvement recommendations</li>'\n                                                                  + '<li style=\"line-height: 150%\">Advanced CLI with syntax highlighting and autocomplete</li>'\n                                                                  + '<li style=\"line-height: 150%\">… and much, much more</li>'\n                                                                  + '</ul>' }\n                RowLayout {\n                    Layout.fillWidth: true\n                    Layout.alignment: Qt.AlignHCenter\n                    Layout.margins: 20;\n\n                    BetterButton {\n                        text: qsTranslate(\"RESP\",\"Download from Snapcraft\")\n                        onClicked: Qt.openUrlExternally(\"https://snapcraft.io/redisinsight\")\n                        visible: PlatformUtils.isLinux()\n                    }\n\n                    BetterButton {\n                        text: qsTranslate(\"RESP\",\"Download from Flathub\")\n                        onClicked: Qt.openUrlExternally(\"https://flathub.org/apps/details/com.redis.RedisInsight\")\n                        visible: PlatformUtils.isLinux()\n                    }\n\n                    BetterButton {\n                        text: qsTranslate(\"RESP\",\"Download from Microsoft Store\")\n                        onClicked: Qt.openUrlExternally(\"https://apps.microsoft.com/store/detail/redisinsight/XP8K1GHCB0F1R2\")\n                        visible: PlatformUtils.isWindows()\n                    }\n\n                    BetterButton {\n                        text: qsTranslate(\"RESP\",\"Download from AppStore\")\n                        onClicked: Qt.openUrlExternally(\"https://apps.apple.com/us/app/redisinsight/id6446987963\")\n                        visible: PlatformUtils.isOSX()\n                    }\n\n                    BetterButton {\n                        text: PlatformUtils.isWindows() ? qsTranslate(\"RESP\",\"Download Installer\") : qsTranslate(\"RESP\",\"Download DMG\")\n                        onClicked: Qt.openUrlExternally(\"https://redis.com/redis-enterprise/redis-insight/\")\n                        visible: !PlatformUtils.isLinux()\n                    }\n                }\n\n\n               RichTextWithLinks { Layout.fillWidth: true; html: '<div style=\"font-size:15px; line-height: 120%\">Thank you for being a RESP.app user! '\n                                                                 + ' To transfer your connections to RedisInsight seamlessly, please follow the <a href=\"https://resp.app/migration-guide/\">migration guide</a>. If you face any migration issues, please contact <a href=\"mailto:igor@resp.app\">igor@resp.app</a>.</div>'}\n\n            }\n        }\n                \n    }\n}\n"
  },
  {
    "path": "src/qml/app.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls.Styles 1.1\nimport QtQml.Models 2.2\nimport QtQuick.Window 2.2\nimport Qt.labs.settings 1.0\nimport QtQuick.Dialogs 1.3 as LegacyDialogs\nimport \".\"\nimport \"./common\"\nimport \"./common/platformutils.js\" as PlatformUtils\nimport \"./value-editor/\"\nimport \"./value-editor/editors/formatters/\"\nimport \"./connections\"\nimport \"./connections-tree\"\nimport \"./console\"\nimport \"./server-actions\"\nimport \"./bulk-operations\"\nimport \"./settings\"\n\nApplicationWindow {\n    id: approot\n    visible: true\n    objectName: \"rdm_qml_root\"\n    title: \"RESP.app - GUI for Redis® \" + Qt.application.version\n    width: 1180\n    height: 800\n    minimumWidth: 1000\n    minimumHeight: 600\n\n    property bool darkModeEnabled: sysPalette.base.hslLightness < 0.4\n\n    property double wRatio : (width * 1.0) / (Screen.width * 1.0)\n    property double hRatio : (height * 1.0) / (Screen.height * 1.0)\n\n    property var currentValueFormatter\n    property var embeddedFormatters\n\n    ValueFormatters {\n        id: valueFormattersModel        \n    }\n\n\n    Component.onCompleted: {\n        if (hRatio > 1 || wRatio > 1) {\n            console.log(\"Ratio > 1.0. Resize main window.\")\n            width = Screen.width * 0.9\n            height = Screen.height * 0.8\n        }\n\n        if (Qt.platform.os == \"windows\") {\n            x = Screen.width / 2 - width / 2\n            y = Screen.height / 2 - height / 2\n        }\n\n        appSplitView.restoreState(windowSettings.splitView)\n    }\n\n    Component.onDestruction: windowSettings.splitView = appSplitView.saveState()\n\n    Settings {\n        id: windowSettings\n        category: \"windows_settings\"\n        property alias width: approot.width\n        property alias height: approot.height\n        property var splitView\n    }\n\n    Settings {\n        id: appSettings\n        category: \"app\"\n        property string valueEditorFont\n        property string valueEditorFontSize\n        property int valueSizeLimit: 1500000\n    }\n\n    Settings {\n        id: defaultFormatterSettings\n        category: \"formatter_overrides\"\n    }\n\n    Settings {\n        id: defaultCompressionSettings\n        category: \"compression_overrides\"\n    }\n\n    SystemPalette {\n        id: sysPalette\n    }\n\n    SystemPalette {\n        id: inactiveSysPalette\n        colorGroup: SystemPalette.Inactive\n    }\n\n    SystemPalette {\n        id: disabledSysPalette\n        colorGroup: SystemPalette.Disabled\n    }\n\n    QuickStartDialog {\n        id: quickStartDialog\n        objectName: \"rdm_qml_quick_start_dialog\"\n\n        width: PlatformUtils.isOSX() ? 600 : approot.width * 0.8\n    }\n\n    Loader {\n        id: settingsDialog\n\n        asynchronous: true\n        source: \"settings/GlobalSettings.qml\"\n    }\n\n    Loader {\n        id: extServerSettingsDialog\n\n        asynchronous: true\n        source: \"extension-server/ExtensionServerSettings.qml\"\n    }\n\n    ConnectionSettignsDialog {\n        id: connectionSettingsDialog\n\n        objectName: \"rdm_connection_settings_dialog\"\n\n        onTestConnection: {\n            connectionsManager.testConnectionSettings(settings, connectionTested)\n        }\n\n        function connectionTested(result) {\n            if (result) {\n                hideLoader()\n                showMsg(qsTranslate(\"RESP\",\"Successful connection to redis-server\"))\n            } else {\n                hideLoader()\n                showError(qsTranslate(\"RESP\",\"Can't connect to redis-server\"))\n            }\n        }\n        onSaveConnection: connectionsManager.updateConnection(settings)\n    }\n\n    AskSecretDialog {\n        id: askSecretDialog\n    }\n\n    ConnectionGroupDialog {\n        id: connectionGroupDialog\n\n        objectName: \"rdm_connection_group_dialog\"\n\n        onAddNewGroup: {\n            connectionsManager.addNewGroup(name)\n        }\n\n        onEditGroup: {\n            connectionsManager.updateGroup(group)\n        }\n    }\n\n    Loader {\n        id: notification\n\n        property var icon\n        property string text\n        property string details\n\n        function showError(msg, details=\"\") {\n            icon = LegacyDialogs.StandardIcon.Warning\n            text = msg\n            notification.details = details\n            sourceComponent = notificationTemplate\n        }\n\n        function showMsg(msg) {\n            icon = LegacyDialogs.StandardIcon.Information\n            text = msg\n            details = \"\"\n            sourceComponent = notificationTemplate\n        }\n\n        onLoaded: {\n            item.open()\n        }\n\n        Component {\n            id: notificationTemplate\n\n            OkDialog {\n                objectName: \"rdm_qml_error_dialog\"\n                visible: false\n\n                icon: notification.icon\n                text: notification.text\n                detailedText: notification.details\n\n                onVisibleChanged: {\n                    if (!visible) {\n                        notification.sourceComponent = undefined\n                    }\n                }\n            }\n        }\n    }\n\n    AddKeyDialog {\n        id: addNewKeyDialog        \n    }\n\n    Connections {\n        target: serverStatsModel\n        ignoreUnknownSignals: true\n        function onError(error) { notification.showError(error) }\n    }\n\n    Connections {\n        target: keyFactory\n\n        function onNewKeyDialog(r) {\n            addNewKeyDialog.request = r\n            addNewKeyDialog.open()\n        }\n    }\n\n    BulkOperationsDialog {\n        id: bulkOperationDialog\n    }\n\n    Connections {\n        target: bulkOperations\n\n        function onOpenDialog(operationName) {\n            bulkOperationDialog.operationName = operationName\n            bulkOperationDialog.open()\n        }\n    }\n\n    Connections {\n        target: appEvents\n\n        function onError(msg) {\n            notification.showError(msg)\n        }\n\n        function onPythonLoaded() {\n            valueFormattersModel.loadEmbeddedFormatters();            \n            valueFormattersModel.updateRWFormatters();\n        }\n\n\n        function onExternalFormattersLoaded() {\n            valueFormattersModel.loadExternalFormatters();\n            valueFormattersModel.updateRWFormatters();\n        }\n    }\n\n    Connections {\n        target: connectionsManager\n\n        function onEditConnection(config) {\n            connectionSettingsDialog.settings = config\n            connectionSettingsDialog.open()\n        }\n\n        function onEditConnectionGroup(group) {\n            connectionGroupDialog.group = group\n            connectionGroupDialog.open()\n        }\n\n        function onConnectionsLoaded() {\n            if (connectionsManager.size() === 0)\n                quickStartDialog.open()\n        }\n\n        function onAskUserForConnectionSecret(config, id) {\n            console.log(\"Ask user for secret\", config.name, id)\n\n            askSecretDialog.secretId = id;\n            askSecretDialog.config = config;\n            askSecretDialog.open()\n            askSecretDialog.forceFocus()\n        }\n    }\n\n    header: AppToolBar {}\n\n    Rectangle {\n        id: appWrapper\n        anchors.fill: parent\n        color: sysPalette.base\n        border.color: sysPalette.mid\n        border.width: 1\n\n    BetterSplitView {\n        id: appSplitView\n        anchors.fill: parent\n        anchors.topMargin: 1\n        orientation: Qt.Horizontal\n\n        ColumnLayout {\n            id: connectionsTreeWrapper\n            SplitView.fillHeight: true\n            SplitView.minimumWidth: 404\n            SplitView.minimumHeight: 500\n\n            BetterTreeView {\n                id: connectionsTree\n\n                Layout.fillHeight: true\n                Layout.fillWidth: true\n            }\n\n            RowLayout {\n                Layout.fillWidth: true\n                Layout.margins: 10\n\n                BetterButton {\n                    id: addConnectionGroupBtn\n                    objectName: \"rdm_add_group_btn\"\n                    iconSource: PlatformUtils.getThemeIcon(\"add.svg\")\n                    text: qsTranslate(\"RESP\", \"Add Group\")\n\n                    Layout.fillWidth: true\n\n                    visible: sortButton.visible\n\n                    onClicked: {\n                        connectionGroupDialog.group = undefined\n                        connectionGroupDialog.open()\n                    }\n                }\n\n                BetterButton {\n                    id: sortButton\n                    objectName: \"rdm_regroup_connections_btn\"\n                    text: qsTranslate(\"RESP\", \"Regroup connections\")\n\n                    iconSource: PlatformUtils.getThemeIcon(\"sort.svg\")\n\n                    Layout.fillWidth: true\n\n                    onClicked: {\n                        connectionsTree.sortConnections = true\n                        connectionsTree.selection.clear()\n                        connectionsTree.backgroundVisible = true\n\n                        connectionsManager.collapseRootItems()\n\n                        sortButton.visible = false\n                    }\n                }\n\n                BetterButton {\n                    id: sortApplyButton\n                    objectName: \"rdm_exit_regroup_mode_btn\"\n                    Layout.fillWidth: true\n\n                    text: qsTranslate(\"RESP\", \"Exit Regroup Mode\")\n                    visible: !sortButton.visible\n\n                    iconSource: PlatformUtils.getThemeIcon(\"ok.svg\")\n\n                    onClicked: {\n                        connectionsTree.sortConnections = false\n                        connectionsTree.backgroundVisible = false\n                        connectionsManager.applyGroupChanges()\n                        sortButton.visible = true\n                    }\n                }\n            }\n        }\n\n        ColumnLayout {\n            SplitView.fillWidth: true\n            SplitView.fillHeight: true\n            TabBar {\n                id: tabBar\n                objectName: \"rdm_main_tab_bar\"\n                Layout.fillWidth: true\n                Layout.preferredHeight: 30\n\n                background: Rectangle {\n                    color: sysPalette.base\n                }\n\n                onCountChanged: {\n                    updateTimer.start()\n                }\n\n                function activateTabButton(item) {\n                    for (var btnIndex in contentChildren) {\n                        if (contentChildren[btnIndex] == item) {\n                            currentIndex = btnIndex;\n                            break;\n                        }\n                    }\n                }\n\n                Timer {\n                    id: updateTimer\n                    interval: 50;\n                    running: false;\n                    repeat: false\n                    onTriggered: {\n                        if (tabBar.count > 0) {\n                            tabs.activateTab(tabBar.itemAt(tabBar.currentIndex).tabRef)\n\n                            if (tabBar.currentIndex == 0 ) {\n                                tabBar.currentIndex = -1\n                                tabBar.currentIndex = 0\n                            }\n                        }\n                    }\n                }\n            }\n\n            StackLayout {\n                id: tabs\n                objectName: \"rdm_qml_tabs\"\n\n                Layout.fillHeight: true\n                Layout.fillWidth: true\n                Layout.minimumWidth: 550\n                Layout.minimumHeight: 30\n\n                onCountChanged: {\n                    if (count === 1) {\n                        currentIndex = 0;\n                    }\n                }\n\n                function activateTab(item) {\n                    var realIndex = 0;\n                    for (var tIndex in tabs.children) {\n                        if (!tabs.children[tIndex].__isTab) {\n                            continue;\n                        }\n\n                        if (tabs.children[tIndex] === item) {\n                            tabs.currentIndex = realIndex;\n                            item.activate();\n                            break;\n                        }\n\n                        realIndex++;\n                    }\n                }\n\n                WelcomeTab {\n                    id: welcomeTab\n                    clip: true\n                    objectName: \"rdm_qml_welcome_tab\"\n                    visible: tabs.count == 1\n                }\n\n                ServerActionTabs {\n                    objectName: \"rdm_qml_server_info_tabs\"\n                    model: serverStatsModel\n                }\n\n                ValueTabs {\n                    objectName: \"rdm_qml_value_tabs\"\n                    model: valuesModel\n                }\n\n                Consoles {\n                    objectName: \"rdm_qml_console_tabs\"\n                    model: consoleModel\n                }\n            }\n\n            Connections {\n                target: valuesModel\n                ignoreUnknownSignals: true\n\n                function onTabError(index, error) {\n                    if (index != -1)\n                        tabs.currentIndex = index\n\n                    notification.showError(error)\n                }\n            }\n        }\n        }\n    }\n\n    Drawer {\n        id: logDrawer\n        dragMargin: 0\n        width: 0.66 * approot.width\n        height: approot.height\n        position: 0.3\n        edge: Qt.LeftEdge\n        background: Rectangle {\n            color: sysPalette.base\n            border.color: sysPalette.mid\n        }\n\n        LogView {\n            anchors.fill: parent\n            eventsModel: appEvents\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/qml/bulk-operations/BulkOperationsDialog.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Dialogs 1.3\nimport \"./../common/\"\nimport \"../common/platformutils.js\" as PlatformUtils\n\nBetterDialog {\n    id: root\n    title: qsTranslate(\"RESP\",\"Bulk Operations Manager\")\n\n    footer: null\n\n    property string operationName: bulkOperations.operationName\n\n    property int firstColSize: PlatformUtils.isScalingDisabled()? 300 : 250\n\n    standardButtons: StandardButton.NoButton\n\n    function loadKeys() {\n        bulkOperations.getAffectedKeys()\n    }\n\n    onVisibleChanged: {\n        if (visible == false) {\n            bulkOperations.clearOperation();\n            resetKeysPreview()\n        } else {\n            targetConnection.model = bulkOperations.getTargetConnections()\n        }\n    }\n\n    function resetKeysPreview() {\n        keysPreview.visible = false\n        btnShowAffectedKeys.visible = true\n        spacer.visible = true\n    }\n\n    function setMetadata() {\n        bulkOperations.setOperationMetadata(\n                    {\n                        \"ttl\": ttlValue.value,\n                        \"replace\": replaceKeys.checked ? \"replace\": \"\",\n                        \"path\": rdbPath.path,\n                        \"db\": rdbDb.value\n                    }\n                    )\n    }\n\n    function showError(title, text, details) {\n        uiBlocker.visible = false\n        bulkErrorNotification.title = title\n        bulkErrorNotification.text = text\n        if (details) {\n            bulkErrorNotification.detailedText = details\n        } else {\n            bulkErrorNotification.detailedText = \"\"\n        }\n\n        bulkErrorNotification.open()\n    }\n\n    function validate() {\n        if (root.operationName == \"rdb_import\" && !qmlUtils.fileExists(rdbPath.path)) {\n            rdbPath.validationError = true\n            showError(qsTranslate(\"RESP\",\"Invalid RDB path\"), qsTranslate(\"RESP\",\"Please specify valid path to RDB file\"), \"\")\n            return false;\n        }\n        rdbPath.validationError = false\n        return true;\n    }\n\n    contentItem: Rectangle {\n        id: contentWrapper\n        implicitWidth: PlatformUtils.isScalingDisabled() ? 1100 : 900\n        implicitHeight: PlatformUtils.isScalingDisabled() ? 650 : 600\n        color: sysPalette.base\n\n        Control {\n            palette: approot.palette\n            anchors.fill: parent\n\n            state: root.operationName\n\n            states: [\n                State {\n                    name: \"delete_keys\"\n                    PropertyChanges { target: operationLabel; text: qsTranslate(\"RESP\",\"Delete keys\") }\n                    PropertyChanges { target: actionButton; text:  qsTranslate(\"RESP\",\"Delete keys\") }\n                    PropertyChanges { target: ttlField; visible: false }\n                    PropertyChanges { target: replaceKeysField; visible: false }\n                    PropertyChanges { target: targetConnectionSettings; visible: false }\n                    PropertyChanges { target: rdbImportFields; visible: false; }\n                },\n                State {\n                    name: \"ttl\"\n                    PropertyChanges { target: operationLabel; text: qsTranslate(\"RESP\",\"Set TTL for multiple keys\") }\n                    PropertyChanges { target: actionButton; text: qsTranslate(\"RESP\",\"Set TTL\") }\n                    PropertyChanges { target: ttlField; visible: true }\n                    PropertyChanges { target: replaceKeysField; visible: false }\n                    PropertyChanges { target: targetConnectionSettings; visible: false }\n                    PropertyChanges { target: rdbImportFields; visible: false; }\n                },\n                State {\n                    name: \"copy_keys\"\n                    PropertyChanges { target: operationLabel; text: qsTranslate(\"RESP\",\"Copy keys to another database\") }\n                    PropertyChanges { target: actionButton; text:  qsTranslate(\"RESP\",\"Copy keys\") }\n                    PropertyChanges { target: ttlField; visible: true }\n                    PropertyChanges { target: replaceKeysField; visible: true }\n                    PropertyChanges { target: targetConnectionSettings; visible: true }\n                    PropertyChanges { target: rdbImportFields; visible: false; }\n                },\n\n                State {\n                    name: \"rdb_import\"\n                    PropertyChanges { target: operationLabel; text: qsTranslate(\"RESP\",\"Import data from rdb file\") }\n                    PropertyChanges { target: actionButton; text:  qsTranslate(\"RESP\",\"Import\") }\n                    PropertyChanges { target: ttlField; visible: false }\n                    PropertyChanges { target: replaceKeysField; visible: false }\n                    PropertyChanges { target: targetConnectionSettings; visible: false }\n                    PropertyChanges { target: rdbImportFields; visible: true; }\n                }\n            ]\n\n            ColumnLayout {\n                anchors.fill: parent\n                anchors.margins: 20\n\n                BetterLabel {\n                    id: operationLabel\n                    font.pixelSize: 20\n                }\n\n                Rectangle {\n                    color: sysPalette.mid\n                    Layout.preferredHeight: 1\n                    Layout.fillWidth: true\n                }\n\n                Item {\n                    Layout.preferredHeight: 5\n                }\n\n                GridLayout {\n                    id: sourceConnectionSettings\n                    columns: 2\n\n                    Layout.fillWidth: true\n\n                    BetterLabel {\n                        text: qsTranslate(\"RESP\",\"Redis Server:\")\n                        Layout.preferredWidth: root.firstColSize\n                        Layout.preferredHeight: 25\n                    }\n\n                    BetterLabel {\n                        Layout.fillWidth: true\n                        Layout.preferredHeight: 25\n                        text: bulkOperations.connectionName\n                    }\n\n                    BetterLabel {\n                        text: qsTranslate(\"RESP\",\"Database number:\")\n                        Layout.preferredWidth: root.firstColSize\n                        Layout.preferredHeight: 25\n                    }\n\n                    BetterLabel {\n                        Layout.fillWidth: true\n                        Layout.preferredHeight: 25\n                        text: bulkOperations.dbIndex\n                    }\n\n                    GridLayout {\n                        id: rdbImportFields\n                        columns: 2\n                        Layout.rowSpan: 2\n                        Layout.columnSpan: 2\n                        Layout.fillWidth: true\n\n                        BetterLabel {\n                            id: rdbPathLabel\n                            text: qsTranslate(\"RESP\",\"Path to RDB file:\")\n                            Layout.preferredWidth: root.firstColSize\n                        }\n\n                        FilePathInput {\n                            id: rdbPath\n\n                            Layout.fillWidth: true\n\n                            objectName: \"rdm_bulk_operations_dialog_rdb_path\"\n\n                            placeholderText: qsTranslate(\"RESP\",\"Path to dump.rdb file\")\n                            nameFilters: [ \"RDB (*.rdb)\" ]\n                            title: qsTranslate(\"RESP\",\"Select dump.rdb\")\n                            path: \"\"\n                            onPathChanged: {\n                                console.log(rdbPath.path)\n                            }\n                        }\n\n                        BetterLabel {\n                            id: rdbDbLabel\n                            text: qsTranslate(\"RESP\",\"Select DB in RDB file:\")\n                            Layout.preferredWidth: root.firstColSize\n                        }\n\n                        BetterSpinBox {\n                            id: rdbDb\n\n                            Layout.fillWidth: true\n\n                            from: 0\n                            to: 10000000\n                            value: 0\n                            onValueChanged: {\n                                setMetadata()\n                                root.resetKeysPreview()\n                            }\n                        }\n                    }\n\n                    BetterLabel {\n                        text: root.operationName == \"rdb_import\"? qsTranslate(\"RESP\",\"Import keys that match <b>regex</b>:\") : qsTranslate(\"RESP\",\"Key pattern:\")\n                        Layout.preferredWidth: root.firstColSize\n                    }\n\n                    BetterTextField {\n                        objectName: \"rdm_bulk_operations_dialog_key_pattern\"\n                        Layout.fillWidth: true\n                        text: bulkOperations.keyPattern\n                        onTextChanged: {\n                            bulkOperations.keyPattern = text\n                            root.resetKeysPreview()\n                        }\n                    }\n\n                    RowLayout {\n                        id: ttlField\n\n                        Layout.fillWidth: true\n                        Layout.columnSpan: 2\n\n                        BetterLabel {\n                            text: \"New TTL value (seconds):\"\n                            Layout.preferredWidth: root.firstColSize\n                        }\n\n                        BetterSpinBox {\n                            id: ttlValue\n                            objectName: \"rdm_bulk_operations_dialog_ttl_value\"\n\n                            Layout.fillWidth: true\n\n\n                            from: -1\n                            to: 10000000\n                            value: 0\n                        }\n                    }\n                }\n\n                GridLayout {\n                    id: targetConnectionSettings\n                    columns: 2\n                    Layout.fillWidth: true\n                    visible: bulkOperations.multiConnectionOperation()\n\n                    BetterLabel {\n                        Layout.preferredWidth: root.firstColSize\n                        text: qsTranslate(\"RESP\",\"Destination Redis Server:\")\n                    }\n\n                    BetterComboBox {\n                        id: targetConnection\n                        objectName: \"rdm_bulk_operations_dialog_connection_combobox\"\n                        Layout.fillWidth: true\n                    }\n\n                    BetterLabel {\n                        Layout.preferredWidth: root.firstColSize\n                        text: qsTranslate(\"RESP\",\"Destination Redis Server Database Index:\")\n                    }\n\n                    BetterSpinBox {\n                        id: targetDatabaseIndex\n\n                        Layout.fillWidth: true\n\n                        objectName: \"rdm_bulk_operations_dialog_target_db_index\"\n\n                        from: 0\n                        to: 10000000\n                        value: 0\n                    }\n\n                    RowLayout{\n                        id: replaceKeysField\n\n                        Layout.columnSpan: 2\n\n                        BetterLabel {\n                            text: \"Replace existing keys in target db:\"\n                            Layout.preferredWidth: root.firstColSize\n                        }\n\n                        BetterCheckbox {\n                            id: replaceKeys\n                            objectName: \"rdm_bulk_operations_dialog_replace_keys\"\n                            Layout.fillWidth: true\n                        }\n                    }\n                }\n\n                Item { Layout.preferredHeight: 10 }\n\n                BetterButton {\n                    id: btnShowAffectedKeys\n                    text: root.operationName == \"rdb_import\"? qsTranslate(\"RESP\",\"Show matched keys\") : qsTranslate(\"RESP\",\"Show Affected keys\")\n                    onClicked: {\n                        if (!validate()) {\n                            return;\n                        }\n\n                        uiBlocker.visible = true\n                        setMetadata()\n                        root.loadKeys()\n                        btnShowAffectedKeys.visible = false\n                        spacer.visible = false\n                        keysPreview.visible = true\n                    }\n                }\n\n                ColumnLayout {\n                    id: keysPreview\n                    Layout.fillWidth: true\n                    Layout.fillHeight: true\n\n                    visible: false\n\n                    BetterLabel {\n                        text: root.operationName == \"rdb_import\"? qsTranslate(\"RESP\",\"Matched keys:\")  : qsTranslate(\"RESP\",\"Affected keys:\")\n                    }\n\n                    FastTextView {\n                        id: affectedKeysListView\n                        color: sysPalette.base\n\n                        border.color: sysPalette.shadow\n                        border.width: 1\n\n                        Layout.fillWidth: true\n                        Layout.fillHeight: true\n\n                        Connections {\n                            target: bulkOperations\n\n                            function onAffectedKeys(r) {\n                                console.log(\"Affected keys loaded\")\n                                affectedKeysListView.model = r\n                                uiBlocker.visible = false\n                            }\n\n                            function onOperationFinished() {\n                                affectedKeysListView.model = []\n                                uiBlocker.visible = false\n                                bulkSuccessNotification.text = qsTranslate(\"RESP\",\"Bulk Operation finished.\")\n                                bulkSuccessNotification.open()\n                            }\n\n                            function onError(e, details) {\n                                showError(qsTranslate(\"RESP\",\"Bulk Operation finished with errors\"), e, details)\n                            }\n                        }\n                    }\n                }\n\n                Item { id: spacer; Layout.fillHeight: true }\n\n                RowLayout {\n                    Layout.fillWidth: true\n\n                    Item { Layout.fillWidth: true; }\n\n                    BetterButton {\n                        id: actionButton\n                        objectName: \"rdm_bulk_operations_dialog_action_button\"\n\n\n                        onClicked: {\n                            if (!validate()) {\n                                return;\n                            }\n\n                            setMetadata()\n                            bulkConfirmation.open()\n                        }\n                    }\n\n                    BetterButton {\n                        text: qsTranslate(\"RESP\",\"Cancel\")\n                        onClicked: root.close()\n                    }\n                }\n            }\n\n\n            Rectangle {\n                id: uiBlocker\n                visible: false\n                anchors.fill: parent\n                color: Qt.rgba(0, 0, 0, 0.1)\n\n                Item {\n                    anchors.fill: parent\n\n                    ColumnLayout {\n                        anchors.centerIn: parent;\n\n                        BusyIndicator { running: true }\n                        BetterLabel {\n                            text: {\n                                if (bulkOperations.operationProgress > 0)\n                                    return qsTranslate(\"RESP\",\"Processed: \") + bulkOperations.operationProgress\n                                else {\n                                    return qsTranslate(\"RESP\", \"Getting list of affected keys...\")\n                                }\n                            }\n                        }\n                    }\n                }\n\n                MouseArea { anchors.fill: parent }\n            }\n\n            Loader {\n                id: bulkErrorNotification\n\n                property var icon: StandardIcon.Warning\n                property string title\n                property string text\n                property string detailedText\n\n                Component {\n                    id: bulkNotificationTemplate\n\n                    OkDialog {\n                        modality: Qt.NonModal\n                        title: bulkErrorNotification.title\n                        icon: bulkErrorNotification.icon\n                        text: bulkErrorNotification.text\n                        detailedText: bulkErrorNotification.detailedText\n\n                        onVisibleChanged: {\n                            if (!visible) {\n                                bulkErrorNotification.sourceComponent = undefined\n                            }\n                        }\n                    }\n                }\n\n                onLoaded: {\n                    item.open()\n                }\n\n                function open() {\n                    sourceComponent = bulkNotificationTemplate\n                }\n            }\n\n            OkDialogOverlay {\n                id: bulkSuccessNotification\n\n                title: qsTranslate(\"RESP\",\"Success\")\n\n                x: (root.width - width) / 2\n                y: (root.height - height) / 3\n\n                visible: false\n                onAccepted: cleanUp()\n\n                onVisibleChanged: {\n                    if (visible == false)\n                        cleanUp()\n                }\n\n                function cleanUp() {\n                    bulkOperations.clearOperation();\n                    uiBlocker.visible = false\n                    root.close()\n                }\n            }\n\n            BetterMessageDialog {\n                id: bulkConfirmation\n\n                x: (root.width - width) / 2\n                y: (root.height - height) / 3\n\n                title: qsTranslate(\"RESP\", \"Confirmation\")\n                text: qsTranslate(\"RESP\", \"Do you really want to perform bulk operation?\")\n                onYesClicked: {\n                    uiBlocker.visible = true\n                    bulkOperations.runOperation(targetConnection.currentIndex, targetDatabaseIndex.value)\n                }\n                visible: false\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/qml/common/AddressInput.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.3\nimport \".\"\n\nRowLayout {    \n    property alias placeholderText: textField.placeholderText\n    property alias host: textField.text\n    property alias port: portField.value\n    property alias validationError: textField.validationError\n\n    BetterTextField {\n        id: textField\n        objectName: \"rdm_connection_address_host_field\"\n        Layout.fillWidth: true        \n    }\n\n    BetterLabel { text: \":\" }\n\n    BetterSpinBox {\n        id: portField\n        objectName: \"rdm_connection_address_port_field\"\n        from: 1\n        to: 10000000\n        value: 22\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterButton.qml",
    "content": "import QtQuick 2.9\nimport QtQuick.Controls 2.3\n\nButton {\n    id: root\n\n    property string iconSource\n    property string tooltip\n\n    icon.source: root.iconSource\n    icon.width: 18\n    icon.height: 18\n    icon.color: \"transparent\"\n    implicitHeight: 30\n    opacity: root.enabled ? 1.0 : 0.8\n    font.capitalization: Font.Capitalize\n\n    palette.button: sysPalette.button\n    palette.brightText: sysPalette.highlightedText\n    palette.windowText: sysPalette.text\n    palette.buttonText: enabled ? sysPalette.text : disabledSysPalette.text\n    palette.highlight: sysPalette.highlight\n\n    MouseArea {\n            id: mouseArea\n            anchors.fill: parent\n            onPressed: mouse.accepted = false\n            cursorShape: Qt.PointingHandCursor\n    }\n\n    BetterToolTip {\n        title: root.tooltip\n    }\n}\n\n"
  },
  {
    "path": "src/qml/common/BetterCheckbox.qml",
    "content": "import QtQuick 2.9\nimport QtQuick.Controls 2.3\n\nCheckBox {\n    id: checkBox\n\n    palette.windowText: sysPalette.windowText\n\n    indicator: Rectangle {\n           implicitWidth: 16\n           implicitHeight: 16\n           x: checkBox.leftPadding\n           y: parent.height / 2 - height / 2\n           radius: 3\n           color: checkBox.down || !checkBox.enabled ? sysPalette.dark : sysPalette.base\n           border.color: sysPalette.dark\n\n           Rectangle {\n               width: 10\n               height: 10\n               x: 3\n               y: 3\n               radius: 2\n               color: checkBox.down ? sysPalette.dark : sysPalette.text\n               visible: checkBox.checked\n           }\n       }\n}\n"
  },
  {
    "path": "src/qml/common/BetterComboBox.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Controls 2.13\n\nComboBox {\n    id: root\n    implicitHeight: 30\n\n    palette.base: sysPalette.base\n    palette.button: sysPalette.button\n    palette.text: sysPalette.text\n    palette.buttonText: sysPalette.text\n    palette.highlightedText: sysPalette.buttonText\n    palette.highlight: sysPalette.highlight\n    palette.mid: sysPalette.mid\n    palette.dark: sysPalette.dark\n    palette.window: sysPalette.window\n\n    function selectItem(txt) {        \n        var res = _select(txt);\n\n        if (res >= 0) {\n            activated(res)\n        }\n    }\n\n    function _select(txt) {\n        var index = find(txt)\n\n        console.log(\"Index:\", index)\n\n        if (index !== -1) {\n            currentIndex = index;\n        }\n\n        return index\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterDialog.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Controls 2.13\nimport \".\"\n\nDialog {\n    id: root\n\n    x: (approot.width - width) / 2\n    y: (approot.height - height) / 3\n    parent: Overlay.overlay\n\n    modal: true\n\n    onRejected: {\n        root.close()\n    }\n\n    background: Rectangle {\n        color: sysPalette.base\n        border.color: sysPalette.mid\n    }\n\n    header: BetterLabel {\n        text: root.title\n        visible: root.title\n        elide: Label.ElideRight\n        font.bold: true\n        padding: 12\n        background: Rectangle {\n            x: 1; y: 1\n            width: parent.width - 2\n            height: parent.height - 1\n            color: sysPalette.window\n        }\n    }\n\n    footer: BetterDialogButtonBox {\n        BetterButton {\n            text: qsTranslate(\"RESP\",\"Save\")\n            DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole\n        }\n\n        BetterButton {\n            text: qsTranslate(\"RESP\",\"Cancel\")\n            onClicked: root.close()\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterDialogButtonBox.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Controls 2.13\n\nDialogButtonBox {\n  spacing: 3\n\n  background: Rectangle {\n      implicitHeight: 40\n      x: 1; y: 1\n      width: parent.width - 2\n      height: parent.height - 2\n      color: sysPalette.base\n  }\n}\n"
  },
  {
    "path": "src/qml/common/BetterGroupbox.qml",
    "content": "import QtQuick.Controls 2.3\n\nGroupBox {\n    id: root\n    property string labelText\n    property alias checked: checkBox.checked\n\n    palette.windowText: sysPalette.windowText\n    palette.mid: sysPalette.mid\n\n    spacing: 1\n    padding: 1\n\n    label: BetterCheckbox {\n            id: checkBox\n            objectName: \"checkbox\"\n            text: root.labelText\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterLabel.qml",
    "content": "import QtQuick 2.9\nimport QtQuick.Controls 2.3\n\nLabel {\n    color: sysPalette.text\n}\n"
  },
  {
    "path": "src/qml/common/BetterMenu.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.15\n\nMenu {\n    background: Rectangle {\n        implicitWidth: 220\n        implicitHeight: 40\n        color: sysPalette.button\n        border.color: sysPalette.mid\n        radius: 2\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterMenuItem.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.15\n\nMenuItem {\n    palette.windowText: sysPalette.text\n    palette.midlight: sysPalette.midlight\n    palette.light: sysPalette.light\n}\n"
  },
  {
    "path": "src/qml/common/BetterMessageDialog.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Controls 2.13\nimport \".\"\n\nBetterDialog {\n    id: root\n\n    implicitWidth: label.contentWidth + 50\n\n    property alias text: label.text\n\n    BetterLabel {\n        id: label\n        anchors.fill: parent\n        anchors.margins: 10\n    }\n\n    signal yesClicked\n\n    footer: BetterDialogButtonBox {\n        spacing: 3\n\n        BetterButton {\n            text: qsTranslate(\"RESP\",\"Yes\")\n            DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole\n            onClicked: {\n                root.yesClicked()\n            }            \n        }\n\n        BetterButton {\n            text: qsTranslate(\"RESP\",\"No\")\n            onClicked: root.close()\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterRadioButton.qml",
    "content": "import QtQuick 2.9\nimport QtQuick.Controls 2.3\n\nRadioButton {\n    id: root\n\n    property bool allowUncheck: false\n\n    property bool _uncheck: false\n\n    onPressed: {\n        if (allowUncheck && checked) {\n            _uncheck = true\n        }\n    }\n\n    palette.windowText: sysPalette.windowText\n\n    onReleased: {\n        if (_uncheck) {\n            checked = false\n            _uncheck = false\n        }\n    }\n\n    indicator: Rectangle {\n           implicitWidth: 16\n           implicitHeight: 16\n           x: root.leftPadding\n           y: parent.height / 2 - height / 2\n           radius: 8\n           color: root.down ? sysPalette.dark : sysPalette.base\n           border.color: sysPalette.dark\n\n           Rectangle {\n               width: 10\n               height: 10\n               x: 3\n               y: 3\n               radius: 5\n               color: root.down ? sysPalette.dark : sysPalette.text\n               visible: root.checked\n           }\n       }\n}\n"
  },
  {
    "path": "src/qml/common/BetterSpinBox.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.13\n\nSpinBox {\n    id: control\n    implicitHeight: 30\n    editable: true\n    textFromValue: renderText\n\n    function renderText(value, locale) { return value }\n\n    palette.text: sysPalette.text\n    palette.highlight: sysPalette.highlight\n    palette.highlightedText: sysPalette.highlightedText\n    palette.button : sysPalette.button\n    palette.base : sysPalette.base\n    palette.mid: disabledSysPalette.highlight\n    palette.buttonText: sysPalette.dark\n\n    contentItem: TextInput {\n        id: spinBoxTextInput\n        z: 2\n        text: control.displayText\n\n        font: control.font\n        color: control.palette.text\n        selectionColor: control.palette.highlight\n        selectedTextColor: control.palette.highlightedText\n        horizontalAlignment: Qt.AlignHCenter\n        verticalAlignment: Qt.AlignVCenter\n\n        readOnly: !control.editable\n        selectByMouse: control.editable\n        validator: control.validator\n        inputMethodHints: control.inputMethodHints\n\n        Rectangle {\n            x: -6 - (control.down.indicator ? 1 : 0)\n            y: -6\n            width: control.width - (control.up.indicator ? control.up.indicator.width - 1 : 0) - (control.down.indicator ? control.down.indicator.width - 1 : 0)\n            height: control.height\n            visible: control.activeFocus\n            color: \"transparent\"\n            border.color: control.palette.highlight\n            border.width: 2\n        }\n    }\n\n    onFocusChanged: {\n        if (focus == true) {\n            spinBoxTextInput.selectAll()\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterSplitView.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.13\n\nSplitView {\n    handle: Rectangle {\n        implicitWidth: parent.orientation == Qt.Horizontal ? 3 : parent.width\n        implicitHeight: parent.orientation == Qt.Vertical ? 3 : parent.height\n        border.color: sysPalette.midlight\n        border.width: 1\n        color: sysPalette.mid\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterTab.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Controls 2.3\n\nItem {\n\n    property bool __isTab: true\n\n    signal activate()\n\n}\n"
  },
  {
    "path": "src/qml/common/BetterTabButton.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.13\n\nTabButton {\n    id: root\n\n    implicitHeight: 30\n    leftPadding: 5\n    spacing: 0\n    icon.width: 18\n    icon.height: 18\n    icon.color: \"transparent\"\n\n    property var self\n    property var tabRef\n    property string tooltip\n\n    signal closeClicked()\n\n    onClicked: {\n        tabs.activateTab(tabRef)\n    }\n\n    palette.brightText: sysPalette.text\n    palette.dark: sysPalette.button\n    palette.mid: sysPalette.window\n    palette.window: sysPalette.base\n    palette.windowText: sysPalette.windowText\n\n    ImageButton {\n        anchors.right: parent.right\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.rightMargin: {\n            var spacing = (parent.width - parent.contentItem.implicitWidth) / 2 - 20;\n            return Math.max(spacing, 0)\n        }\n\n        onClicked: {\n            root.closeClicked()\n        }\n    }\n\n    BetterToolTip {\n        title: tooltip\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterTabView.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.3\nimport QtQuick.Controls.Styles 1.1\nimport \".\"\nimport \"./platformutils.js\" as PlatformUtils\n\nTabView {\n    id: root\n\n    style: TabViewStyle {\n        tab: Rectangle {\n            color: \"#cccccc\"\n            implicitWidth: layout.implicitWidth + 3\n            implicitHeight: 30\n            radius: 3\n\n            Rectangle {\n                id: content\n                color: styleData.selected ? \"white\" : \"#e2e2e2\"\n                anchors.fill: parent\n                anchors.topMargin: control.tabPosition == Qt.TopEdge ? 1 : 0\n                anchors.rightMargin: 1\n                anchors.leftMargin: 1\n                anchors.bottomMargin: control.tabPosition == Qt.BottomEdge ? 1 : 0\n\n                RowLayout {\n                    id: layout\n                    anchors.fill: parent\n                    anchors.rightMargin: 8\n\n                    Item { Layout.preferredWidth: 3 }\n\n                    AnimatedImage {\n                        source: {\n                            var icon = root.getTab(styleData.index) !== undefined ? root.getTab(styleData.index).icon : \"\"\n\n                            if (icon && icon.indexOf(\".gif\") > -1) {\n                                visible = true\n                                return icon\n                            } else {\n                                visible = false\n                                return \"\"\n                            }\n                        }\n\n                        width: 20\n                        height: 20\n                    }\n\n                    Image {\n                        source: {\n                            var icon = root.getTab(styleData.index) !== undefined ? root.getTab(styleData.index).icon : \"\"\n\n                            if (icon && icon.indexOf(\".gif\") == -1) {\n                                visible = true\n                                return icon\n                            } else {\n                                visible = false\n                                return \"\"\n                            }\n                        }\n\n                        sourceSize.width: 20\n                        sourceSize.height: 20\n                    }\n\n                    Text {\n                        color: sysPalette.text\n                        Layout.fillWidth: true\n                        Layout.minimumWidth: implicitWidth\n                        text: styleData.title\n                    }\n\n                    Item {\n                        visible: root.getTab(styleData.index) !== undefined && !root.getTab(styleData.index).closable\n                        Layout.preferredWidth: 3\n                    }\n\n                    ImageButton {\n                        visible: root.getTab(styleData.index) !== undefined && root.getTab(styleData.index).closable\n\n                        Layout.preferredWidth: 18\n                        Layout.preferredHeight: 18\n\n                        imgSource: PlatformUtils.getThemeIcon(\"clear.svg\")\n                        onClicked: root.getTab(styleData.index).closeTab(styleData.index)\n                    }\n                }\n            }\n        }\n\n        frame: Rectangle {\n            color: \"#e2e2e2\"\n\n            Rectangle {\n                color: \"white\"\n                anchors.fill: parent\n                anchors.bottomMargin: 1\n                anchors.leftMargin: 1\n                anchors.rightMargin: 1\n                anchors.topMargin: 1\n            }\n        }\n\n        leftCorner: Item {}\n        rightCorner: Item {}\n    }\n\n}\n"
  },
  {
    "path": "src/qml/common/BetterTextField.qml",
    "content": "import QtQuick 2.9\nimport QtQuick.Controls 2.3\n\nTextField {\n    id: control\n    property bool validationError: false\n    property int  bgImplicitWidth: 200\n    property int  bgImplicitHeight: 30\n    property string tooltip\n\n    selectByMouse: true\n\n    color: sysPalette.text\n    selectionColor: sysPalette.highlight\n    selectedTextColor: sysPalette.highlightedText\n\n    background: Rectangle {\n        implicitWidth: control.bgImplicitWidth\n        implicitHeight: control.bgImplicitHeight\n        color: control.enabled? sysPalette.button : inactiveSysPalette.button\n        border.width: control.validationError? 2 : 1\n        border.color: {\n            if (control.validationError || !control.acceptableInput)\n                return \"#d12f24\"\n\n            return control.activeFocus ? sysPalette.highlight : sysPalette.mid\n        }\n    }\n\n    BetterToolTip {\n        title: control.tooltip\n    }\n}\n"
  },
  {
    "path": "src/qml/common/BetterToolTip.qml",
    "content": "import QtQuick 2.9\nimport QtQuick.Controls 2.3\n\nToolTip {\n    property string title\n\n    visible: title && hovered\n    contentItem: Text {\n           text: title\n           color: sysPalette.text\n    }\n\n    background: Rectangle {\n        border.width: 1\n        color: sysPalette.base\n        border.color: sysPalette.mid\n    }\n}\n"
  },
  {
    "path": "src/qml/common/ColorInput.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.3\nimport QtQuick.Dialogs 1.3\nimport \"./platformutils.js\" as PlatformUtils\n\n\nRowLayout {\n    id: root\n\n    property alias placeholderText: textField.placeholderText\n    property alias color: textField.text\n    property alias title: dialog.title\n    property alias validationError: textField.validationError\n\n    function reset() {\n        color = \"\"\n        dialog.color = \"\"\n    }\n\n    Rectangle {\n        implicitWidth: 30\n        implicitHeight: 30\n        color: textField.text? textField.text: \"transparent\"\n        border.color:  sysPalette.highlight\n        border.width: 1\n\n        MouseArea {\n            anchors.fill: parent\n            onClicked: dialog.open()\n        }\n    }\n\n    BetterTextField {\n        id: textField\n        objectName: root.objectName? root.objectName + \"_text\" : \"\"\n        Layout.fillWidth: true\n    }\n\n    BetterButton {\n        implicitHeight: 30\n        objectName: root.objectName? root.objectName + \"_button\" : \"\"\n        text: qsTranslate(\"RESP\",\"Select\")\n        onClicked: dialog.open()\n    }\n\n    ColorDialog {\n        id: dialog\n        onAccepted: textField.text = dialog.color\n    }\n}\n"
  },
  {
    "path": "src/qml/common/FastTextView.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.13\nimport \"./platformutils.js\" as PlatformUtils\n\nRectangle {\n    id: root\n\n    property alias model: listView.model\n    property alias delegate: listView.delegate\n    property bool showLineNumbers: true\n\n    function positionViewAtEnd() {\n        listView.positionViewAtEnd()\n    }\n\n    function dumpText() {        \n        var allStrings = \"\";\n        for (var id in root.model) {\n            allStrings += root.model[id] + \"\\n\"\n        }\n        return allStrings\n    }\n\n    ScrollView {\n        anchors.fill: parent\n        anchors.margins: 10\n\n        ScrollBar.vertical.policy: ScrollBar.AlwaysOn\n\n        ListView {\n            id: listView\n            width: root.width - 20\n\n            delegate: TextEdit {\n                color: sysPalette.text\n                width: listView.width\n                readOnly: true\n                selectByMouse: true\n                text: {\n                    if (root.showLineNumbers) {\n                        return (index+1) + \". \" + modelData\n                    } else {\n                        return modelData\n                    }\n                }\n                wrapMode: Text.WrapAnywhere\n            }\n        }\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.RightButton\n\n        onClicked: {\n            menu.x = mouseX\n            menu.y = mouseY\n            menu.open()\n        }\n    }\n\n    Menu {\n        id: menu\n        z: 255\n\n        MenuItem {\n            text: \"Copy\"\n            icon.source: PlatformUtils.getThemeIcon(\"copy.svg\")\n            icon.color: \"transparent\"\n\n            onTriggered: {                \n                qmlUtils.copyToClipboard(root.dumpText())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/common/FilePathInput.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.3\nimport Qt.labs.platform 1.1\nimport \"./platformutils.js\" as PlatformUtils\n\n\nRowLayout {\n    id: root\n\n    property alias placeholderText: textField.placeholderText\n    property alias path: textField.text\n    property alias nameFilters: fileDialog.nameFilters\n    property alias title: fileDialog.title\n    property alias validationError: textField.validationError\n\n    BetterTextField {\n        id: textField\n        objectName: root.objectName? root.objectName + \"_text\" : \"\"\n        readOnly: PlatformUtils.isOSX()\n        Layout.fillWidth: true\n    }\n\n    BetterButton {\n        implicitHeight: 30\n        objectName: root.objectName? root.objectName + \"_button\" : \"\"\n        text: qsTranslate(\"RESP\",\"Select File\")\n        onClicked: fileDialog.open()\n    }\n\n    FileDialog {\n        id: fileDialog\n        fileMode: FileDialog.OpenFile\n        onAccepted: textField.text = qmlUtils.getPathFromUrl(fileDialog.file)\n    }\n}\n"
  },
  {
    "path": "src/qml/common/ImageButton.qml",
    "content": "import QtQuick 2.9\nimport QtQuick.Controls 2.3\nimport \"./platformutils.js\" as PlatformUtils\n\nBetterButton {\n    id: root\n\n    implicitWidth: 18\n    implicitHeight: 18\n    property alias imgWidth: img.width\n    property alias imgHeight: img.height\n    property alias imgSource: img.source\n    property alias iconSource: img.source\n    property bool showBorder: false\n    property bool imgStickTop: false\n\n    MouseArea {\n            id: mouseArea\n            anchors.fill: parent\n            onPressed: mouse.accepted = false\n            cursorShape: Qt.PointingHandCursor\n    }\n\n    Image {\n        id: img\n        anchors.horizontalCenter: parent.horizontalCenter\n        anchors.verticalCenter: root.text || root.imgStickTop ? null : parent.verticalCenter\n        source: PlatformUtils.getThemeIcon(\"clear.svg\")\n        width: 18\n        height: 18\n        sourceSize.width: width * 2\n        sourceSize.height: height * 2\n        opacity: root.enabled? 1.0: 0.8\n    }\n\n    contentItem: Text {\n      text: root.text\n      font: root.font\n      opacity: enabled ? 1.0 : 0.8\n      color: root.down ? sysPalette.highlightedText : enabled ? sysPalette.text : disabledSysPalette.text\n      horizontalAlignment: Text.AlignHCenter\n      verticalAlignment: root.imgSource != \"\" ? Text.AlignBottom : Text.AlignVCenter\n      elide: Text.ElideRight\n    }\n\n    background: Rectangle {\n        implicitWidth: root.implicitWidth + 3\n        implicitHeight: root.implicitHeight + 3\n        opacity: root.enabled ? 1 : 0.3\n        color: root.hovered ? sysPalette.highlight : \"transparent\"\n        border.width: root.hovered ? 1 : root.showBorder ? 1 : 0\n        border.color: root.hovered? sysPalette.highlight : sysPalette.mid\n        radius: 5\n   }\n}\n\n"
  },
  {
    "path": "src/qml/common/JsonHighlighter.qml",
    "content": "import QtQuick 2.0\nimport rdm.models 1.0\n\nItem {\n    property alias textDocument: syntaxHighlighter.textDocument\n\n    property bool _darkPalette: sysPalette.base.hslLightness < 0.4\n\n    TextCharFormat {\n        id: keyFormat;\n        foreground: _darkPalette? '#fcfcfc': '#000000'\n        font.family: appSettings.valueEditorFont\n        font.pointSize: appSettings.valueEditorFontSize\n    }\n\n    TextCharFormat {\n        id: numberFormat;\n        foreground: _darkPalette? '#008cff': '#0000ff'\n        font.family: appSettings.valueEditorFont\n        font.pointSize: appSettings.valueEditorFontSize\n    }\n\n    TextCharFormat {\n        id: boolFormat;\n        foreground: _darkPalette? '#d62929': '#b22222'\n        font.family: appSettings.valueEditorFont\n        font.pointSize: appSettings.valueEditorFontSize\n    }\n\n    TextCharFormat {\n        id: stringFormat;\n        foreground: _darkPalette? '#05a605': '#008000'\n        font.family: appSettings.valueEditorFont\n        font.pointSize: appSettings.valueEditorFontSize\n    }\n\n    TextCharFormat {\n        id: nullFormat;\n        foreground: _darkPalette? '#a8a8a8' : '#808080'\n        font.family: appSettings.valueEditorFont\n        font.pointSize: appSettings.valueEditorFontSize\n    }\n\n    SyntaxHighlighter {\n        id: syntaxHighlighter\n\n        onHighlightBlock: {\n            let rx = /(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g\n            let m\n            while ( ( m = rx.exec(text) ) !== null ) {\n                var type = 'number';\n                if (/^\"/.test(m[0])) {\n                    if (/:$/.test(m[0])) {\n                        setFormat(m.index, m[0].length, keyFormat);\n                        continue;\n                    } else {\n                        setFormat(m.index, m[0].length, stringFormat);\n                        continue;\n                    }\n                } else if (/true|false/.test(m[0])) {\n                    setFormat(m.index, m[0].length, boolFormat);\n                    continue;\n                } else if (/null/.test(m[0])) {\n                    setFormat(m.index, m[0].length, nullFormat);\n                    continue;\n                }\n\n                setFormat(m.index, m[0].length, numberFormat);\n                continue;\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/qml/common/LegacyTableView.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 1.4 as LC\nimport \"./platformutils.js\" as PlatformUtils\n\nLC.TableView {\n    rowDelegate: Rectangle {\n        height: 30\n        color: styleData.selected ? sysPalette.highlight : styleData.alternate? sysPalette.alternateBase : \"transparent\"\n    }\n}\n"
  },
  {
    "path": "src/qml/common/NewTextArea.qml",
    "content": "import QtQuick 2.7\nimport \"./platformutils.js\" as PlatformUtils\nimport \".\"\n\nTextEdit {\n    id: root\n    color: sysPalette.text\n    wrapMode: TextEdit.WrapAnywhere\n    font {\n        family: appSettings.valueEditorFont\n        pointSize: appSettings.valueEditorFontSize\n    }\n    selectByMouse: true\n\n    property bool highlightJSON: false\n\n    Loader {\n        source: root.highlightJSON? \"./JsonHighlighter.qml\" : \"\"\n        onLoaded: {\n            if (item) {\n                item.textDocument = root.textDocument\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/common/OkDialog.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Dialogs 1.3\n\nMessageDialog {\n    id: root\n\n    standardButtons: StandardButton.Ok\n}\n"
  },
  {
    "path": "src/qml/common/OkDialogOverlay.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Controls 2.13\nimport \".\"\n\nBetterDialog {\n    id: root\n\n    implicitWidth: label.contentWidth + 50\n\n    property alias text: label.text\n\n    BetterLabel {\n        id: label\n        anchors.fill: parent\n        anchors.margins: 10\n    }\n\n    footer: BetterDialogButtonBox {\n        BetterButton {\n            text: qsTranslate(\"RESP\",\"OK\")\n            DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/common/PasswordInput.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.3\n\nRowLayout {\n    id: root\n\n    property alias placeholderText: textField.placeholderText\n    property alias text: textField.text\n    property alias validationError: textField.validationError\n    property alias checkboxWidth: passwordMask.width\n\n    signal accepted\n\n    function forceFocus() {\n        textField.forceActiveFocus()\n    }\n\n    BetterTextField {\n        id: textField\n        Layout.fillWidth: true\n        echoMode: passwordMask.checked ? TextInput.Normal : TextInput.Password\n\n        onAccepted: root.accepted()\n    }\n\n    BetterCheckbox {\n        id: passwordMask\n        text: qsTranslate(\"RESP\",\"Show password\")\n    }\n}\n"
  },
  {
    "path": "src/qml/common/RichTextWithLinks.qml",
    "content": "import QtQuick 2.0\n\nText {\n    property string html\n    property string styleString: '<style>a {color: \"#1565C0\"; text-decoration: none; vertical-align: top;}</style>'\n\n    textFormat: Qt.RichText\n    text: styleString  + html\n    wrapMode: Text.Wrap\n    onLinkActivated: Qt.openUrlExternally(link)\n    color: sysPalette.windowText\n\n    MouseArea {\n        anchors.fill: parent\n        cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor\n        acceptedButtons: Qt.NoButton\n    }\n}\n"
  },
  {
    "path": "src/qml/common/SaveToFileButton.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.13\nimport Qt.labs.platform 1.1\nimport QtQuick.Layouts 1.1\nimport \"./platformutils.js\" as PlatformUtils\n\nImageButton {\n    id: root\n    iconSource: raw ? PlatformUtils.getThemeIcon(\"binary_file.svg\") : PlatformUtils.getThemeIcon(\"code_file.svg\")\n    tooltip: raw ? qsTranslate(\"RESP\",\"Save Raw Value to File\") : qsTranslate(\"RESP\",\"Save Formatted Value to File\") + \" (\" + shortcutText + \")\"\n\n    property string fileUrl\n    property string folderUrl\n    property string path\n    property string shortcutText: \"\"\n    property bool raw: false\n\n\n    onClicked: saveToFile()\n\n    function saveToFile() {\n        saveValueToFileDialog.open()\n    }\n\n    FileDialog {\n        id: saveValueToFileDialog\n        title: raw ? qsTranslate(\"RESP\",\"Save Raw Value\") : qsTranslate(\"RESP\",\"Save Formatted Value\")\n        nameFilters: [\"All files (*)\"]\n        fileMode: FileDialog.SaveFile\n\n        onAccepted: {\n            root.fileUrl = file\n\n            var path = qmlUtils.getPathFromUrl(file)\n            root.folderUrl = qmlUtils.getUrlFromPath(qmlUtils.getDir(path))\n            root.path = qmlUtils.getNativePath(path)\n            if (raw) {\n                if (qmlUtils.saveToFile(value, root.path)) {\n                    saveToFileConfirmation.open()\n                }\n            } else {\n                if (qmlUtils.saveToFile(textView.model.getText(), root.path)) {\n                    saveToFileConfirmation.open()\n                }\n            }\n        }\n    }\n\n    BetterDialog {\n        id: saveToFileConfirmation\n        title: qsTranslate(\"RESP\",\"Value was saved to file:\")\n        visible: false\n        footer: null\n\n        Rectangle {\n            objectName: \"rdm_save_to_file_confirmation_dialog\"\n            color: sysPalette.base\n            anchors.fill: parent\n\n            implicitWidth: 500\n            implicitHeight: 150\n\n            Control {\n                palette: approot.palette\n                anchors.fill: parent\n                anchors.margins: 15\n\n                ColumnLayout {\n                    anchors.fill: parent\n\n                    TextEdit {\n                        objectName: \"rdm_save_to_file_confirmation_dialog_path\"\n                        Layout.fillWidth: true\n                        text: root.path\n                        color: sysPalette.text\n                        readOnly: true\n                        selectByMouse: true\n                        wrapMode: Text.Wrap\n                    }\n\n                    ColumnLayout {\n                        Layout.fillWidth: true\n\n                        Control {\n                            RowLayout {\n                                Layout.fillHeight: true\n                                spacing: 15\n\n                                RichTextWithLinks {\n                                    Layout.fillWidth: true\n                                    wrapMode: Text.NoWrap\n                                    html: \"<a href='\" + root.fileUrl + \"'>Open File</a>\"\n                                }\n\n                                RichTextWithLinks {\n                                    Layout.fillWidth: true\n                                    wrapMode: Text.NoWrap\n                                    html: \"<a href='\" + root.folderUrl + \"'>Open Folder</a>\"\n                                }\n                            }\n                        }\n                    }\n\n                    RowLayout {\n                        Layout.fillHeight: true\n\n                        Item { Layout.fillWidth: true; }\n\n                        BetterButton {\n                            objectName: \"rdm_save_to_file_confirmation_dialog_ok_btn\"\n                            text: qsTranslate(\"RESP\",\"OK\")\n                            onClicked: saveToFileConfirmation.close()\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/common/SettingsGroupTitle.qml",
    "content": "import QtQuick.Controls 2.2\n\nBetterLabel {\n    font.pixelSize: 15\n    font.bold: true\n}\n"
  },
  {
    "path": "src/qml/common/platformutils.js",
    "content": "\nvar dateTimeFormat = \"yyyy-MM-dd hh:mm:ss.zzz\"\n\nfunction isScalingDisabled() {\n    return (Qt.platform.os == \"windows\" || Qt.platform.os == \"linux\")\n            && screen.devicePixelRatio < 2\n}\n\nfunction isWindows() {\n    return Qt.platform.os == \"windows\"\n}\n\nfunction isLinux() {\n    return Qt.platform.os == \"linux\"\n}\n\nfunction isOSX() {\n    return Qt.platform.os == \"osx\"\n}\n\nfunction isOSXRetina(screen) {\n    return isOSX() && screen.devicePixelRatio> 1\n}\n\nfunction getThemeIcon(icon) {\n    if (sysPalette.base.hslLightness < 0.4) {\n        return \"qrc:/images/dark_theme/\" + icon\n    } else {\n        return \"qrc:/images/light_theme/\" + icon\n    }\n}\n"
  },
  {
    "path": "src/qml/connections/AskSecretDialog.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.13\nimport QtQuick.Dialogs 1.3\nimport QtQuick.Window 2.3\nimport \"./../common\"\n\nDialog {\n    id: root\n\n    modality: Qt.ApplicationModal\n    title: qsTranslate(\"RESP\",\"Enter \" + getSecretName()  + \" to connect to \") + config.name\n\n    property string secretId: \"\"\n    property var config\n\n    function getSecretName() {\n       if (secretId === \"ssh_password\") {\n           return qsTranslate(\"RESP\",\"SSH Passphrase\")\n       } else {\n           return qsTranslate(\"RESP\",\"Unknown\")\n       }\n    }\n\n    function forceFocus() {\n        secretValue.forceFocus()\n    }\n\n    contentItem: Rectangle {\n        color: sysPalette.base\n        implicitHeight: 100\n        implicitWidth: 600\n\n        Control {\n            palette: approot.palette\n            anchors.fill: parent\n\n            ColumnLayout {\n                anchors.fill: parent\n                anchors.margins: 5\n\n                RowLayout {\n                    Layout.fillWidth: true\n\n                    BetterLabel {\n                        text: qsTranslate(\"RESP\",\"Passphrase\")\n                    }\n                    PasswordInput {\n                        id: secretValue\n                        objectName: \"rdm_secret_input\"\n\n                        onTextChanged: root.config.sshPassword = text\n                        onAccepted: {\n                            submitSecretBtn.submit()\n                        }\n                    }\n                }\n\n                RowLayout {\n                    Layout.fillWidth: true\n                    Layout.minimumHeight: 40\n                    Item {\n                        Layout.fillWidth: true\n                    }\n                    BetterButton {\n                        id: submitSecretBtn\n\n                        Layout.preferredWidth: secretValue.checkboxWidth\n\n                        objectName: \"rdm_secret_continue_btn\"\n                        text: qsTranslate(\"RESP\",\"Continue\")\n\n                        function submit() {\n                            if (!secretValue.text) {\n                                return;\n                            }\n\n                            root.close()\n                            connectionsManager.proceedWithConnectionSecret(root.config)\n                        }\n\n                        onClicked: submit()\n                    }\n\n                    BetterButton {\n                        Layout.preferredWidth: secretValue.checkboxWidth\n\n                        objectName: \"rdm_secret_cancel_btn\"\n                        text: qsTranslate(\"RESP\",\"Cancel\")\n                        onClicked: root.close()\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections/ConnectionSettignsDialog.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.13\nimport QtQuick.Window 2.3\nimport \"../common\"\nimport \"../common/platformutils.js\" as PlatformUtils\n\nBetterDialog {\n    id: root\n    title: isNewConnection ? qsTranslate(\"RESP\",\"New Connection Settings\") : qsTranslate(\"RESP\",\"Edit Connection Settings\") + \" \" + settings.name\n\n    footer: null\n\n    property bool isNewConnection: !settings || !settings.name\n    property var settings\n    property string quickStartGuideUrl: \"http://docs.resp.app/en/latest/quick-start/\"\n\n    signal testConnection\n    signal saveConnection(var settings)\n    signal resetSettings\n\n    property var items: []\n    property var sshItems: []\n    property var sslItems: []\n    property var sshEnabled\n    property var sslEnabled\n\n    function cleanStyle() {\n        function clean(items_array) {\n            for (var index=0; index < items_array.length; index++)\n                if (items_array[index].validationError !== undefined)\n                    items_array[index].validationError = false\n        }\n\n        clean(items)\n        clean(sshItems)\n        clean(sslItems)\n        validationWarning.visible = false\n    }\n\n    function validate() {\n\n        cleanStyle()\n\n        function checkItems(items_array) {\n            var errors = 0\n\n            for (var index=0; index < items_array.length; index++) {\n                var value = undefined\n\n                if (items_array[index].text != undefined) {\n                    value = items_array[index].text\n                } else if (items_array[index].host != undefined) {\n                    value = items_array[index].host\n                } else if (items_array[index].path != undefined) {\n                    value = items_array[index].path\n                }\n\n                if (value != undefined && value.length == 0) {\n                    errors++\n                    items_array[index].validationError = true\n                }\n            }\n\n            return errors\n        }\n\n        var errors_count = checkItems(items)\n\n        if (sshEnabled)\n            errors_count += checkItems(sshItems)\n\n        if (sslEnabled)\n            errors_count += checkItems(sslItems)\n\n        return errors_count == 0\n    }\n\n    function hideLoader() {\n        uiBlocker.visible = false\n    }\n\n    function showLoader() {\n        uiBlocker.visible = true\n    }\n\n    function showMsg(msg) {\n        dialog_notification.showMsg(msg)\n    }\n\n    function showError(err) {\n        dialog_notification.showError(err)\n    }\n\n    function isConnectionStringValid(connectionString) {\n        return connectionsManager.isRedisConnectionStringValid(connectionString)\n    }\n\n    function parseConnectionString(connectionString) {\n        return connectionsManager.parseConfigFromRedisConnectionString(connectionString)\n    }\n\n    onVisibleChanged: {\n        if (visible) {\n            connectionSettingsTabBar.currentIndex = isNewConnection ? 0 : 1\n            if (isNewConnection) {\n                connectionStringField.forceActiveFocus()\n            }\n        }\n    }\n\n    contentItem: Rectangle {\n        color: sysPalette.base\n        implicitWidth: PlatformUtils.isScalingDisabled() ? 900 : 650\n        implicitHeight: {\n            if (screen.devicePixelRatio === 1) {\n                return connectionSettingsTabBar.implicitHeight\n                        + sshSettingsGrid.implicitHeight + 350\n            } else {\n                return 610\n            }\n        }\n        Control {\n            palette: approot.palette\n            anchors.fill: parent\n\n            ColumnLayout {\n                anchors.fill: parent\n                anchors.margins: 10\n\n                TabBar {\n                    id: connectionSettingsTabBar\n                    Layout.fillWidth: true\n\n                    palette.brightText: sysPalette.text\n                    palette.dark: sysPalette.button\n                    palette.mid: sysPalette.window\n                    palette.window: sysPalette.base\n                    palette.windowText: sysPalette.windowText\n\n                    TabButton {\n                        id: connectionWizardTabBtn\n                        objectName: \"rdm_connection_settings_dialog_wizard_tab\"\n                        text:  qsTranslate(\"RESP\",\"How to connect\")\n                        visible: isNewConnection\n                        width: visible ? undefined : 0\n                    }\n\n                    TabButton {\n                        objectName: \"rdm_connection_settings_dialog_basic_settings_tab\"\n                        text: qsTranslate(\"RESP\",\"Connection Settings\")\n                    }\n\n                    TabButton {\n                        objectName: \"rdm_connection_settings_dialog_advanced_settings_tab\"\n                        text:  qsTranslate(\"RESP\",\"Advanced Settings\")\n                    }\n                }\n\n                StackLayout {\n                    id: settingsTabs\n                    Layout.fillWidth: true\n                    Layout.fillHeight: true\n                    currentIndex: connectionSettingsTabBar.currentIndex\n\n                    BetterTab {\n                        id: wizardTab\n\n                        Layout.fillWidth: true\n                        Layout.fillHeight: true\n                        Layout.margins: 30\n\n                        ColumnLayout {\n                            anchors.fill: parent\n                            anchors.margins: 5\n                            spacing: 20\n\n                            SettingsGroupTitle {\n                                Layout.fillWidth: true\n                                text: qsTranslate(\"RESP\",\"Create connection from Redis URL\")\n                            }\n\n                            RowLayout {\n                                Layout.fillWidth: true\n\n                                BetterTextField {\n                                    id: connectionStringField\n                                    objectName: \"rdm_connection_settings_dialog_wizard_connection_string_field\"\n                                    Layout.fillWidth: true\n                                    focus: connectionWizardTabBtn.visible\n                                    placeholderText: \"redis://localhost:6379\"\n\n                                    KeyNavigation.tab: importConnectionStringBtn.enabled? importConnectionStringBtn : skipToNextTabBtn\n\n                                    onAccepted: {\n                                        importConnectionStringBtn.clicked()\n                                    }\n\n                                    onTextEdited: validationError = false\n                                }\n\n                                BetterButton {\n                                    id: importConnectionStringBtn\n                                    objectName: \"rdm_connection_settings_dialog_wizard_import_btn\"\n                                    text: qsTranslate(\"RESP\",\"Import\")\n                                    enabled: !!connectionStringField.text\n\n                                    KeyNavigation.tab: skipToNextTabBtn\n\n                                    onClicked: {\n                                        if (!isConnectionStringValid(connectionStringField.text)) {\n                                            connectionStringField.validationError = true\n                                        } else {\n                                            connectionStringField.validationError = false\n                                            root.settings = parseConnectionString(connectionStringField.text)\n                                            connectionSettingsTabBar.currentIndex = 1\n                                            connectionName.forceActiveFocus()\n                                        }\n                                    }\n\n                                    Keys.onEnterPressed: {\n                                        importConnectionStringBtn.clicked()\n                                    }\n\n                                    Keys.onReturnPressed: {\n                                        importConnectionStringBtn.clicked()\n                                    }\n                                }\n                            }\n\n                            RichTextWithLinks {\n                                Layout.fillWidth: true\n                                text: qsTranslate(\"RESP\", \"Learn more about Redis URL:  \")\n                                      + \"<a href='https://www.iana.org/assignments/uri-schemes/prov/redis'>redis://</a>,&nbsp;\"\n                                      + \"<a href='https://www.iana.org/assignments/uri-schemes/prov/rediss'>rediss://</a>\"\n                            }\n\n                            SettingsGroupTitle {\n                                Layout.fillWidth: true\n                                text: qsTranslate(\"RESP\",\"Connection guides\")\n                            }\n\n                            GridLayout {\n                                id: tileGrid\n                                columns: 4\n\n                                Layout.fillWidth: true\n\n                                property int tileSize: 90\n                                property int tileIconSize: 64\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#connect-to-a-local-or-public-redis-server\"\n\n                                    tooltip: url\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    text: qsTranslate(\"RESP\", \"Local or Public Redis\")\n\n                                    showBorder: true\n\n                                    iconSource: \"\"\n                                    onClicked: Qt.openUrlExternally(url)\n                                }\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#connect-to-a-public-redis-server-with-ssl\"\n\n                                    tooltip: url\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    text: qsTranslate(\"RESP\", \"Redis with SSL/TLS\")\n\n                                    showBorder: true\n\n                                    iconSource: \"\"\n                                    onClicked: Qt.openUrlExternally(url)\n                                }\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#connect-to-private-redis-server-via-ssh-tunnel\"\n\n                                    tooltip: url\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    text: qsTranslate(\"RESP\", \"SSH tunnel\")\n\n                                    showBorder: true\n\n                                    iconSource: \"\"\n                                    onClicked: Qt.openUrlExternally(url)\n                                }\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#connect-to-a-unix-socket\"\n\n                                    tooltip: url\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    text: qsTranslate(\"RESP\", \"UNIX socket\")\n\n                                    showBorder: true\n\n                                    iconSource: \"\"\n                                    onClicked: Qt.openUrlExternally(url)\n                                }\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#digital-ocean-managed-redis\"\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    imgWidth: tileGrid.tileIconSize\n                                    imgHeight: tileGrid.tileIconSize\n\n                                    iconSource: \"qrc:/images/digitalocean_logo.svg\"\n\n                                    showBorder: true\n\n                                    tooltip: url\n\n                                    onClicked: Qt.openUrlExternally(url)\n                                }\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#microsoft-azure-redis-cache\"\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    imgWidth: tileGrid.tileIconSize\n                                    imgHeight: tileGrid.tileIconSize\n\n                                    iconSource: \"qrc:/images/azure_logo.svg\"\n\n                                    showBorder: true\n\n                                    tooltip: url\n\n                                    onClicked: Qt.openUrlExternally(url)\n                                }\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#aws-elasticache\"\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    imgWidth: tileGrid.tileIconSize\n                                    imgHeight: tileGrid.tileIconSize\n\n                                    iconSource: approot.darkModeEnabled? \"qrc:/images/aws_logo_white.svg\" : \"qrc:/images/aws_logo.svg\"\n\n                                    showBorder: true\n\n                                    tooltip: url\n\n                                    onClicked: Qt.openUrlExternally(url)\n                                }\n\n                                ImageButton {\n                                    property string url: \"http://docs.resp.app/en/latest/quick-start/#heroku-redis\"\n\n                                    Layout.fillWidth: true\n                                    implicitHeight: tileGrid.tileSize\n\n                                    imgWidth: tileGrid.tileIconSize\n                                    imgHeight: tileGrid.tileIconSize\n\n                                    iconSource: \"qrc:/images/heroku_logo.svg\"\n\n                                    showBorder: true\n\n                                    tooltip: url\n\n                                    onClicked: Qt.openUrlExternally(url)\n                                }                               \n                            }\n\n                            ColumnLayout {\n                                Layout.fillWidth: true\n                                Layout.fillHeight: true\n                                Layout.topMargin: 10\n\n                                SettingsGroupTitle {\n                                    text: qsTranslate(\"RESP\",'Cannot figure out how to connect to your redis-server?')\n                                }\n\n                                RichTextWithLinks {\n                                    Layout.fillWidth: true\n                                    wrapMode: Text.WrapAnywhere\n                                    html: qsTranslate(\"RESP\",'<a href=\"https://docs.resp.app/en/latest/quick-start/\">Read the Docs</a>, '\n                                                      + '<a href=\"mailto:support@resp.app\">Contact Support</a> '\n                                                      + 'or ask for help in our <a href=\"https://t.me/RedisDesktopManager\">Telegram Group</a>')\n                                }\n                            }\n\n                            ColumnLayout {\n                                Layout.fillWidth: true\n                                Layout.fillHeight: true\n                                Layout.topMargin: 10\n\n                                SettingsGroupTitle {\n                                    text: qsTranslate(\"RESP\",\"Don't have running Redis?\")\n                                }\n\n                                RichTextWithLinks {\n                                    Layout.fillWidth: true\n                                    wrapMode: Text.WrapAnywhere\n                                    html: '<a href=\"https://redis.com/try-free/?utm_source=respapp&utm_medium=app&utm_campaign=newconn\">' + qsTranslate(\"RESP\",'Use Redis Cloud: Up to 6 month free with $200 credits') + '</a>'\n                                }\n                            }\n\n                            RowLayout {\n\n                                Item { Layout.fillWidth: true }\n\n                                Item { Layout.fillHeight: true }\n\n                                BetterButton {\n                                    id: skipToNextTabBtn\n                                    text: qsTranslate(\"RESP\",\"Skip\")\n                                    onClicked: {\n                                        connectionSettingsTabBar.currentIndex = 1\n                                    }\n\n                                    Keys.onEnterPressed: {\n                                        skipToNextTabBtn.clicked()\n                                    }\n\n                                    Keys.onReturnPressed: {\n                                        skipToNextTabBtn.clicked()\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    BetterTab {\n                        id: mainTab\n\n                        Layout.fillWidth: true\n                        Layout.fillHeight: true\n                        Layout.margins: 30\n                        clip: true\n\n                        ColumnLayout {\n                            anchors.fill: parent\n                            anchors.margins: 10\n\n                            GridLayout {\n                                objectName: \"rdm_connection_basic_settings\"\n                                columns: 2\n\n                                Layout.fillWidth: true\n\n                                BetterLabel { text: qsTranslate(\"RESP\",\"Name:\") }\n\n                                BetterTextField {\n                                    id: connectionName\n                                    objectName: \"rdm_connection_name_field\"\n                                    Layout.fillWidth: true\n                                    placeholderText: qsTranslate(\"RESP\",\"Connection Name\")\n                                    text: root.settings ? root.settings.name : \"\"\n                                    Component.onCompleted: root.items.push(connectionName)\n                                    onTextChanged: root.settings.name = text\n                                }\n\n                                BetterLabel { text: qsTranslate(\"RESP\",\"Address:\") }\n\n                                AddressInput {\n                                    id: connectionAddress\n                                    objectName: \"rdm_connection_address_input\"\n                                    placeholderText: qsTranslate(\"RESP\",\"redis-server host\")\n                                    host: root.settings ? root.settings.host : \"\"\n                                    port: root.settings ? root.settings.port : 0\n                                    Component.onCompleted: root.items.push(connectionAddress)\n                                    onHostChanged: if (root.settings) root.settings.host = host\n                                    onPortChanged: if (root.settings) root.settings.port = port\n                                }\n\n                                BetterLabel {\n                                    id: windowsLocalhostWarning\n                                    Layout.columnSpan: 2\n                                    text: qsTranslate(\"RESP\", \"For better network performance please use 127.0.0.1\")\n                                    visible: !root.sshEnabled && !root.sslEnabled\n                                             && String(connectionAddress.host).toLowerCase() === \"localhost\"\n                                             && Qt.platform.os == \"windows\"\n\n                                }\n\n                                BetterLabel { text: qsTranslate(\"RESP\",\"Password:\") }\n\n                                PasswordInput {\n                                    id: connectionAuth\n                                    objectName: \"rdm_connection_auth_field\"\n                                    Layout.fillWidth: true\n                                    placeholderText: qsTranslate(\"RESP\",\"(Optional) redis-server authentication password\")\n                                    text: root.settings ? root.settings.auth : \"\"\n                                    onTextChanged: root.settings.auth = text\n                                }\n\n                                BetterLabel { text: qsTranslate(\"RESP\",\"Username:\") }\n\n                                BetterTextField {\n                                    id: connectionUsername\n                                    objectName: \"rdm_connection_username_field\"\n                                    Layout.fillWidth: true\n                                    placeholderText: qsTranslate(\"RESP\",\"(Optional) redis-server authentication username\" + \" (Redis >6.0)\")\n                                    text: root.settings ? root.settings.username : \"\"\n                                    onTextChanged: if (root.settings) root.settings.username = text\n                                }\n                            }\n\n                            Item { Layout.preferredWidth: 10 }\n\n                            SettingsGroupTitle { text: qsTranslate(\"RESP\",\"Security\") }\n\n                            GridLayout {\n                                id: securityGrid\n                                objectName: \"rdm_connection_group_box_security\"\n                                columns: 2\n\n                                RowLayout {\n                                    Layout.fillWidth: true\n                                    Layout.columnSpan: 2\n\n                                    BetterRadioButton {\n                                        id: sslRadioButton\n                                        objectName: \"rdm_connection_security_ssl_radio_button\"\n                                        text: qsTranslate(\"RESP\",\"SSL / TLS\")\n                                        allowUncheck: true\n                                        checked: root.settings ? root.settings.sslEnabled && !root.sshEnabled : false\n                                        Component.onCompleted: root.sslEnabled = Qt.binding(function() { return sslRadioButton.checked })\n                                        onCheckedChanged: {\n                                            root.settings.sslEnabled = checked\n                                            root.cleanStyle()\n                                        }\n                                    }\n\n                                    BetterRadioButton {\n                                        id: sshRadioButton\n                                        objectName: \"rdm_connection_security_ssh_radio_button\"\n                                        text: qsTranslate(\"RESP\",\"SSH Tunnel\")\n                                        allowUncheck: true\n                                        checked: root.settings ? root.settings.useSshTunnel() : false\n                                        Component.onCompleted: root.sshEnabled = Qt.binding(function() { return sshRadioButton.checked })\n                                        onCheckedChanged: {\n                                            root.cleanStyle()\n                                        }\n                                    }\n                                }\n\n                                Item { Layout.preferredWidth: 15 }\n\n                                GridLayout {\n                                    id: tlsSettingsGrid\n                                    objectName: \"rdm_connection_security_ssl_grid\"\n                                    enabled: sslRadioButton.checked\n                                    visible: sslRadioButton.checked\n                                    columns: 2\n                                    Layout.fillWidth: true\n\n                                    BetterLabel { text: qsTranslate(\"RESP\",\"Public Key:\") }\n\n                                    FilePathInput {\n                                        id: sslLocalCertPath\n                                        objectName: \"rdm_connection_security_ssl_local_cert_path_field\"\n                                        Layout.fillWidth: true\n                                        placeholderText: qsTranslate(\"RESP\",\"(Optional) Public Key in PEM format\")\n                                        nameFilters: [ \"Public Key in PEM format (*.pem *.crt)\" ]\n                                        title: qsTranslate(\"RESP\",\"Select public key in PEM format\")\n                                        path: root.settings ? root.settings.sslLocalCertPath : \"\"\n                                        onPathChanged: root.settings.sslLocalCertPath = path\n                                    }\n\n                                    BetterLabel { text: qsTranslate(\"RESP\", \"Private Key\") + \":\" }\n\n                                    FilePathInput {\n                                        id: sslPrivateKeyPath\n                                        objectName: \"rdm_connection_security_ssl_private_key_path_field\"\n                                        Layout.fillWidth: true\n                                        placeholderText: qsTranslate(\"RESP\",\"(Optional) Private Key in PEM format\")\n                                        nameFilters: [ \"Private Key in PEM format (*.pem *.key)\" ]\n                                        title: qsTranslate(\"RESP\",\"Select private key in PEM format\")\n                                        path: root.settings ? root.settings.sslPrivateKeyPath : \"\"\n                                        onPathChanged: root.settings.sslPrivateKeyPath = path\n                                    }\n\n                                    BetterLabel { text: qsTranslate(\"RESP\",\"Authority:\") }\n\n                                    FilePathInput {\n                                        id: sslCaCertPath\n                                        objectName: \"rdm_connection_security_ssl_ca_cert_path_field\"\n                                        Layout.fillWidth: true\n                                        placeholderText: qsTranslate(\"RESP\",\"(Optional) Authority in PEM format\")\n                                        nameFilters: [ \"Authority file in PEM format (*.pem *.crt)\" ]\n                                        title: qsTranslate(\"RESP\",\"Select authority file in PEM format\")\n                                        path: root.settings ? root.settings.sslCaCertPath : \"\"\n                                        onPathChanged: root.settings.sslCaCertPath = path\n                                    }\n\n                                    BetterLabel { text: qsTranslate(\"RESP\",\"Enable strict mode:\")}\n\n                                    BetterCheckbox {\n                                        id: ignoreSSLErrors\n                                        Layout.fillWidth: true\n                                        checked: root.settings ? !root.settings.ignoreSSLErrors : false\n                                        onCheckedChanged: root.settings.ignoreSSLErrors = !checked\n                                    }\n                                }\n\n                                GridLayout {\n                                    id: sshSettingsGrid\n                                    objectName: \"rdm_connection_security_ssh_grid\"\n                                    visible: sshRadioButton.checked\n                                    enabled: sshRadioButton.checked\n                                    columns: 2\n                                    Layout.fillWidth: true\n\n                                    BetterLabel { text: qsTranslate(\"RESP\",\"SSH Address:\") }\n\n                                    AddressInput {\n                                        id: sshAddress\n                                        placeholderText: qsTranslate(\"RESP\",\"Remote Host with SSH server\")\n                                        port: root.settings ? root.settings.sshPort : 22\n                                        host: root.settings ? root.settings.sshHost : \"\"\n                                        Component.onCompleted: root.sshItems.push(sshAddress)\n                                        onHostChanged: root.settings.sshHost = host\n                                        onPortChanged: root.settings.sshPort = port\n                                    }\n\n                                    BetterLabel { text: qsTranslate(\"RESP\",\"SSH User:\") }\n\n                                    BetterTextField {\n                                        id: sshUser\n                                        objectName: \"rdm_connection_security_ssh_user_field\"\n                                        Layout.fillWidth: true\n                                        placeholderText: qsTranslate(\"RESP\",\"Valid SSH User Name\")\n                                        text: root.settings ? root.settings.sshUser : \"\"\n                                        Component.onCompleted: root.sshItems.push(sshUser)\n                                        onTextChanged: root.settings.sshUser = text\n                                    }\n\n                                    BetterCheckbox {\n                                        id: sshAgentCheckbox\n                                        objectName: \"rdm_connection_security_ssh_agent\"\n                                        text: qsTranslate(\"RESP\",\"Use SSH Agent\")\n                                        checked: root.settings ? root.settings.sshAgent : false\n                                        onCheckedChanged: root.settings.sshAgent = checked\n                                    }\n\n                                    FilePathInput {\n                                        id: sshAgentPath\n                                        visible: !(Qt.platform.os === \"windows\" || (PlatformUtils.isOSX() && qmlUtils.isAppStoreBuild()))\n                                        objectName: \"rdm_connection_security_ssh_agent_path_field\"\n                                        Layout.fillWidth: true\n                                        placeholderText: qsTranslate(\"RESP\",\"(Optional) Custom SSH Agent Path\")\n                                        nameFilters: [ \"SSH Agent (*)\" ]\n                                        title: qsTranslate(\"RESP\",\"Select SSH Agent\")\n                                        path: root.settings ? root.settings.sshAgentPath : \"\"\n                                        onPathChanged: root.settings.sshAgentPath = path\n                                    }\n\n                                    RichTextWithLinks {\n                                        visible: PlatformUtils.isOSX() && qmlUtils.isAppStoreBuild()\n                                        Layout.fillWidth: true\n                                        wrapMode: Text.WrapAnywhere\n                                        html: '<a href=\"https://docs.resp.app/en/latest/quick-start/#ssh-agent\">' + qsTranslate(\"RESP\",'Additional configuration is required to enable SSH Agent support') + '</a>'\n                                    }\n\n                                    BetterGroupbox {\n                                        id: sshKeyGroupBox\n                                        labelText: qsTranslate(\"RESP\",\"Private Key\")\n                                        objectName: \"rdm_connection_security_ssh_key_group_box\"\n                                        checked: root.settings ? root.settings.sshPrivateKey : false                                        \n                                        enabled: !sshAgentCheckbox.checked\n                                        opacity: enabled ? 1 : 0.5\n\n                                        Layout.columnSpan: 2\n                                        Layout.fillWidth: true\n\n                                        ColumnLayout {\n                                            anchors.fill: parent\n\n                                            FilePathInput {\n                                                id: sshPrivateKey\n                                                objectName: \"rdm_connection_security_ssh_key_path_field\"\n\n                                                Layout.fillWidth: true\n\n                                                placeholderText: qsTranslate(\"RESP\",\"Path to Private Key in PEM format\")\n                                                nameFilters: [ \"Private key in PEM format (*)\" ]\n                                                title: qsTranslate(\"RESP\",\"Select private key in PEM format\")\n                                                path: root.settings ? root.settings.sshPrivateKey : \"\"\n                                                onPathChanged: root.settings.sshPrivateKey = path\n                                            }\n\n                                            BetterLabel {\n                                                visible: PlatformUtils.isOSX()\n                                                Layout.fillWidth: true;\n                                                text: qsTranslate(\"RESP\",\"<b>Tip:</b> Use <code>⌘ + Shift + .</code> to show hidden files and folders in dialog\") }\n                                        }\n                                    }\n\n                                    BetterGroupbox {\n                                        id: sshPasswordGroupBox\n                                        labelText: sshKeyGroupBox.checked? qsTranslate(\"RESP\",\"Passphrase\") : qsTranslate(\"RESP\",\"Password\")\n                                        objectName: \"rdm_connection_security_ssh_password_group_box\"\n                                        checked: root.settings ? root.settings.sshPassword || root.settings.askForSshPassword : true\n                                        enabled: !sshAgentCheckbox.checked\n                                        opacity: enabled ? 1 : 0.5\n\n                                        Layout.columnSpan: 2\n                                        Layout.fillWidth: true\n\n                                        RowLayout {\n                                            anchors.fill: parent\n\n                                            PasswordInput {\n                                                id: sshPassword\n                                                objectName: \"rdm_connection_security_ssh_password_field\"\n\n                                                Layout.fillWidth: true\n                                                placeholderText: sshKeyGroupBox.checked? qsTranslate(\"RESP\",\"Passphrase for provided private key\")\n                                                                                       : sshAskForPasswordCheckbox.checked?\n                                                                                             qsTranslate(\"RESP\",\"Password request will be prompt prior to connection\")\n                                                                                           : qsTranslate(\"RESP\",\"SSH User Password\")\n                                                text: root.settings ? root.settings.sshPassword : \"\"\n                                                onTextChanged: root.settings.sshPassword = text\n\n                                                enabled: !sshAskForPasswordCheckbox.checked\n                                            }\n\n                                            BetterCheckbox {\n                                                id: sshAskForPasswordCheckbox\n                                                objectName: \"rdm_connection_security_ssh_ask_for_password\"\n                                                text: qsTranslate(\"RESP\",\"Ask for password\")\n                                                checked: root.settings ? root.settings.askForSshPassword : false\n                                                onCheckedChanged: root.settings.askForSshPassword = checked\n                                            }\n                                        }\n                                    }\n\n                                    BetterCheckbox {\n                                        id: sshTLSoverSSHCheckbox\n                                        objectName: \"rdm_connection_security_ssh_tls_over_ssh\"\n                                        Layout.fillWidth: true\n                                        Layout.columnSpan: 2\n                                        text: qsTranslate(\"RESP\",\"Enable TLS-over-SSH (<b>AWS ElastiCache</b> <b>Encryption in-transit</b>)\")\n                                        checked: root.settings ? root.settings.sslEnabled : false\n                                        onCheckedChanged: root.settings.sslEnabled = checked\n\n                                        Connections {\n                                            target: root\n\n                                            function onSslEnabledChanged() {\n                                                // NOTE(u_glide): Workaround for case when user enables plain TLS\n                                                // on existing TLS-over-SSH connection and then selects SSH again.\n                                                if (!root.sslEnabled && root.settings.sshHost\n                                                        && sshTLSoverSSHCheckbox.checked) {\n                                                    root.settings.sslEnabled = true\n                                                }\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n\n                            Item { Layout.fillHeight: true }\n                        }\n\n                    }\n\n                    BetterTab {\n                        Layout.fillWidth: true\n                        Layout.fillHeight: true\n                        Layout.margins: 30\n\n                        GridLayout {\n                            anchors.fill: parent\n                            anchors.margins: 10\n\n                            columns: 2\n\n                            SettingsGroupTitle {\n                                text: qsTranslate(\"RESP\",\"Keys loading\")\n                                Layout.columnSpan: 2\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Default filter:\") }\n\n                            BetterTextField\n                            {\n                                id: keysPattern\n                                Layout.fillWidth: true\n                                placeholderText: qsTranslate(\"RESP\",\"Pattern which defines loaded keys from redis-server\")\n                                text: root.settings ? root.settings.keysPattern : \"\"\n                                Component.onCompleted: root.items.push(keysPattern)\n                                onTextChanged: if (root.settings) { root.settings.keysPattern = text }\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Namespace Separator:\") }\n\n                            BetterTextField\n                            {\n                                id: namespaceSeparator\n                                Layout.fillWidth: true\n                                objectName: \"rdm_advanced_settings_namespace_separator_field\"\n                                placeholderText: qsTranslate(\"RESP\",\"Separator used for namespace extraction from keys\")\n                                text: root.settings ? root.settings.namespaceSeparator : \"\"\n                                onTextChanged: if (root.settings) { root.settings.namespaceSeparator = text }\n                            }\n\n                            SettingsGroupTitle {\n                                text: qsTranslate(\"RESP\",\"Timeouts & Limits\")\n                                Layout.columnSpan: 2\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Connection Timeout (sec):\") }\n\n                            BetterSpinBox {\n                                id: executeTimeout\n                                Layout.fillWidth: true\n                                from: 10\n                                to: 100000\n                                value: {\n                                    return root.settings ? (root.settings.executeTimeout / 1000.0) : 0\n                                }\n                                onValueChanged: if (root.settings) { root.settings.executeTimeout = value * 1000 }\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Execution Timeout (sec):\")}\n\n                            BetterSpinBox {\n                                id: connectionTimeout\n                                Layout.fillWidth: true\n                                from: 10\n                                to: 100000\n                                value: root.settings ? (root.settings.connectionTimeout / 1000.0) : 0\n                                onValueChanged: if (root.settings) { root.settings.connectionTimeout = value * 1000 }\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Databases discovery limit:\") }\n\n                            BetterSpinBox {\n                                id: dbScanLimit\n                                Layout.fillWidth: true\n                                from: 1\n                                to: 100000\n                                value: {\n                                    return root.settings ? root.settings.databaseScanLimit : 1\n                                }\n                                onValueChanged: if (root.settings) { root.settings.databaseScanLimit = value }\n                            }\n\n                            SettingsGroupTitle {\n                                text: qsTranslate(\"RESP\",\"Cluster\")\n                                Layout.columnSpan: 2\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Change host on cluster redirects:\")}\n\n                            BetterCheckbox {\n                                id: overrideClusterHost\n                                Layout.fillWidth: true\n                                checked: root.settings ? root.settings.overrideClusterHost : false\n                                onCheckedChanged: if (root.settings) { root.settings.overrideClusterHost = checked }\n                            }\n\n                            SettingsGroupTitle {\n                                text: qsTranslate(\"RESP\",\"Formatters\")\n                                Layout.columnSpan: 2\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Default value formatter:\")}\n\n                            RowLayout {\n                                Layout.fillWidth: true\n\n                                BetterComboBox {\n                                    id: defaultFormatterLogicSelector\n\n                                    Layout.fillWidth: true\n\n                                    property int customFormatterIndex: 2\n\n                                    ListModel {\n                                        id: defaultFormatterOptionsModel\n\n                                        Component.onCompleted: {\n                                            append({ value: \"auto\", text: qsTranslate(\"RESP\", \"Auto detect (JSON / Plain Text / HEX)\") })\n                                            append({ value: \"last_selected\", text: qsTranslate(\"RESP\", \"Last selected\") })\n                                            append({ value: \"specific\", text: qsTranslate(\"RESP\", \"Select formatter ...\") })\n                                            defaultFormatterLogicSelector.currentIndex = 0\n                                        }\n                                    }\n\n                                    textRole: \"text\"\n                                    model: defaultFormatterOptionsModel\n\n                                    Connections {\n                                        target: root\n\n                                        function onSettingsChanged(s) {\n                                            if (!root.settings) {\n                                                defaultFormatterLogicSelector.currentIndex = 0;\n                                                return;\n                                            }\n\n                                            if (root.settings.defaultFormatter !== \"auto\"\n                                                    && root.settings.defaultFormatter !== \"last_selected\") {\n                                                defaultFormatterSelector._select(root.settings.defaultFormatter)\n                                                defaultFormatterLogicSelector.currentIndex = defaultFormatterLogicSelector.customFormatterIndex;\n                                                return;\n                                            }\n\n                                            defaultFormatterLogicSelector.currentIndex = root.settings.defaultFormatter === \"auto\"? 0 : 1;\n                                        }\n                                    }\n\n                                    onActivated: {\n                                        if (currentIndex != customFormatterIndex) {\n                                            root.settings.defaultFormatter = defaultFormatterOptionsModel.get(currentIndex)[\"value\"]\n                                        }\n                                    }\n                                }\n\n                                BetterComboBox {\n                                    id: defaultFormatterSelector\n\n                                    visible: defaultFormatterLogicSelector.currentIndex == 2\n                                    model: valueFormattersModel\n                                    textRole: \"name\"\n\n                                    onActivated: {\n                                        root.settings.defaultFormatter = currentText\n                                    }\n                                }\n                            }\n\n                            SettingsGroupTitle {\n                                text: qsTranslate(\"RESP\",\"Appearance\")\n                                Layout.columnSpan: 2\n                            }\n\n                            BetterLabel { text: qsTranslate(\"RESP\",\"Icon color:\")}\n\n                            ColorInput {\n                                id: iconsColor\n                                Layout.fillWidth: true\n\n                                color: root.settings ? root.settings.iconColor : \"\"\n                                onColorChanged: root.settings.iconColor = color\n\n                                Connections {\n                                    target: root\n\n                                    function onResetSettings() {\n                                        iconsColor.reset();\n                                    }\n                                }\n                            }\n\n                            Item {\n                                Layout.columnSpan: 2\n                                Layout.fillHeight: true\n                                Layout.fillWidth: true\n                            }\n                        }\n                    }\n                }\n\n                RowLayout {\n                    Layout.fillWidth: true\n                    Layout.preferredHeight: 30\n\n                    visible: !isNewConnection || isNewConnection && connectionSettingsTabBar.currentIndex != 0\n\n                    BetterButton {\n                        objectName: \"rdm_connection_settings_dialog_test_btn\"\n                        iconSource: PlatformUtils.getThemeIcon(\"offline.svg\")\n                        text: qsTranslate(\"RESP\",\"Test Connection\")\n                        onClicked: {\n                            showLoader()\n                            root.testConnection(root.settings)\n                        }\n                    }\n\n                    BetterButton {\n                        iconSource: PlatformUtils.getThemeIcon(\"help.svg\")\n                        text: qsTranslate(\"RESP\",\"Quick Start Guide\")\n                        onClicked: Qt.openUrlExternally(root.quickStartGuideUrl)\n                        visible: !isNewConnection\n                    }\n\n                    Item { Layout.fillWidth: true }\n\n                    RowLayout {\n                        id: validationWarning\n                        visible: false\n                        Layout.fillWidth: true\n\n                        Image {\n                            width: 15\n                            height: 15\n                            sourceSize.width: 30\n                            sourceSize.height: 30\n                            source: PlatformUtils.getThemeIcon(\"alert.svg\")\n                        }\n                        BetterLabel {\n                            text: qsTranslate(\"RESP\",\"Invalid settings detected!\")\n                        }\n                    }\n\n                    Item { Layout.fillWidth: true }\n\n                    BetterButton {\n                        objectName: \"rdm_connection_settings_dialog_ok_btn\"\n                        text: qsTranslate(\"RESP\",\"OK\")\n                        onClicked: {\n                            if (root.validate()) {\n\n                                if (!sshKeyGroupBox.checked)\n                                    root.settings.sshPrivateKey = \"\"\n                                if (!sshPasswordGroupBox.checked)\n                                    root.settings.sshPassword = \"\"\n\n                                if (sshAgentCheckbox.checked) {\n                                    root.settings.sshPrivateKey = \"\"\n                                    root.settings.sshPassword = \"\"\n                                } else {\n                                    root.settings.sshAgentPath = \"\"\n                                }\n\n                                root.saveConnection(root.settings)\n                                root.settings = connectionsManager.createEmptyConfig()\n                                root.resetSettings()\n                                root.close()\n                            } else {\n                                validationWarning.visible = true\n                            }\n                        }\n                    }\n\n                    BetterButton {\n                        objectName: \"rdm_connection_settings_dialog_cancel_btn\"\n                        text: qsTranslate(\"RESP\",\"Cancel\")\n                        onClicked: {\n                            root.settings = connectionsManager.createEmptyConfig()\n                            root.cleanStyle()\n                            root.resetSettings()\n                            root.close()\n                        }\n                    }\n                }\n            }\n\n            Rectangle {\n                id: uiBlocker\n                visible: false\n                anchors.fill: parent\n                color: Qt.rgba(0, 0, 0, 0.1)\n\n                Item {\n                    anchors.fill: parent\n                    BusyIndicator { anchors.centerIn: parent; running: true }\n                }\n\n                MouseArea {\n                    anchors.fill: parent\n                }\n            }\n            OkDialogOverlay {\n                id: dialog_notification\n\n                objectName: \"rdm_qml_connection_settings_error_dialog\"\n                visible: false                                              \n\n                function showError(msg) {                    \n                    text = msg\n                    title = qsTranslate(\"RESP\",\"Error\")\n                    open()\n                }\n\n                function showMsg(msg) {                    \n                    text = msg\n                    title = qsTranslate(\"RESP\",\"Success\")\n                    open()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections-tree/BetterTreeView.qml",
    "content": "import QtQuick 2.14\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.4\nimport QtQuick.Controls.Styles 1.4\nimport QtQml.Models 2.2\nimport QtQuick.Window 2.2\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../common\"\nimport \".\"\n\nTreeView {\n    id: root\n    alternatingRowColors: false\n    headerVisible: false\n    focus: true\n    horizontalScrollBarPolicy: Qt.ScrollBarAsNeeded\n    verticalScrollBarPolicy: Qt.ScrollBarAsNeeded\n\n    model: connectionsManager\n\n    property bool sortConnections: false\n\n    backgroundVisible: false\n\n    /**\n      * NOTE(u_glide): Dirty hack to use build-in macOS style for scrollbars on all platforms\n      */\n    Component.onCompleted: {\n        if (!PlatformUtils.isOSX()) {\n            __scroller.verticalScrollBar.__panel.on = true\n        }\n    }\n\n    Connections {\n        target: !PlatformUtils.isOSX()? __scroller.verticalScrollBar.__panel : null\n\n        function onOnChanged() {\n            if (!__scroller.verticalScrollBar.__panel.on) {\n                __scroller.verticalScrollBar.__panel.on = true\n            }\n        }\n    }\n\n    Component {\n        id: patchedBackground\n\n        Item {\n            implicitWidth: 25\n            implicitHeight: 200\n        }\n    }\n    // hack-end\n\n    style: TreeViewStyle {\n        frame: Item {}\n\n        indentation: 12\n\n        rowDelegate: Rectangle {\n            height: PlatformUtils.isOSXRetina(Screen) ? 25 : 30\n            color: styleData.selected ? sysPalette.highlight : \"transparent\"\n        }\n\n        transientScrollBars: true\n\n        backgroundColor: sysPalette.button\n\n        scrollBarBackground: PlatformUtils.isOSX()? TreeViewStyle.scrollBarBackground : patchedBackground\n    }\n\n    TableViewColumn {\n        id: itemColumn\n        title: \"item\"\n        role: \"metadata\"\n\n        delegate: TreeItemDelegate {\n            id: itemRoot            \n            treeRoot: root\n            sortConnections: root.sortConnections\n        }\n    }    \n\n    selectionMode: SelectionMode.SingleSelection\n\n    selection: ItemSelectionModel {\n        id: connectionTreeSelectionModel\n        model: connectionsManager\n    }\n\n    onClicked: connectionsManager && connectionsManager.sendEvent(index, \"click\")\n    onExpanded: connectionsManager.setExpanded(index)\n    onCollapsed: connectionsManager.setCollapsed(index)\n\n    Connections {\n        target: connectionsManager\n\n        function onExpand(index) {\n            if (root.isExpanded(index))\n                return\n\n            root.expand(index)\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections-tree/ConnectionGroupDialog.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.13\nimport QtQuick.Window 2.3\nimport \"../common\"\nimport \"../common/platformutils.js\" as PlatformUtils\n\nBetterDialog {\n    id: root\n    title: group? qsTranslate(\"RESP\",\"Edit Connections Group\") + group.name : qsTranslate(\"RESP\",\"Add New Connections Group\")\n    visible: false\n    property var group\n\n    footer: null\n\n    signal addNewGroup(string name)\n    signal editGroup(var group)\n\n    Item {\n        anchors.fill: parent\n        implicitHeight: 150\n        implicitWidth: 600\n\n        ColumnLayout {\n            anchors.fill: parent\n            anchors.margins: 5\n\n            BetterLabel {\n                text: qsTranslate(\"RESP\",\"Group Name:\")\n            }\n            BetterTextField {\n                id: groupName\n                Layout.fillWidth: true\n                objectName: \"rdm_connections_group_field\"\n                text: group? group.name : ''\n            }\n\n            RowLayout {\n                Layout.fillWidth: true\n                Layout.minimumHeight: 40\n                Item {\n                    Layout.fillWidth: true\n                }\n                BetterButton {\n                    objectName: \"rdm_connections_group_save_btn\"\n                    text: qsTranslate(\"RESP\",\"Save\")\n\n                    onClicked: {                        \n                        if (group) {\n                            group.name = groupName.text\n                            root.editGroup(group)\n                        } else {\n                            root.addNewGroup(groupName.text)\n                        }\n\n                        root.close()\n                    }\n                }\n\n                BetterButton {\n                    text: qsTranslate(\"RESP\",\"Cancel\")\n                    onClicked: root.close()\n                }\n            }\n            Item { Layout.fillWidth: true }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections-tree/TreeItemDelegate.qml",
    "content": "import QtQuick 2.14\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.4\nimport QtQuick.Controls.Styles 1.4\nimport QtQml.Models 2.2\nimport QtQuick.Window 2.2\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../common\"\n\nFocusScope {\n    id: root\n\n    property bool sortConnections: false\n    property var treeRoot\n\n    function __getIconColorMappings(userColor) {\n        if (!userColor)\n            return {}\n\n        if (approot.darkModeEnabled) {\n            return {\n                \"unknown\": {\n                    \"#979798\": qmlUtils.changeColorAlpha(userColor, 150)\n                },\n                \"standalone\": {\n                    \"#DC423C\": userColor,\n                    \"#9A2928\": qmlUtils.changeColorAlpha(userColor, 200)\n                },\n                \"cluster\": {\n                    \"#5856D6\": userColor\n                },\n                \"sentinel\": {\n                    \"#DC423C\": userColor\n                },\n                \"database\": {\n                    \"#DC423C\": userColor\n                }\n            }\n        } else {\n            return {\n                \"unknown\": {\n                    \"#B8BEC9\": qmlUtils.changeColorAlpha(userColor, 150)\n                },\n                \"standalone\": {\n                    \"#DC423C\": userColor,\n                    \"#9A2928\": qmlUtils.changeColorAlpha(userColor, 200)\n                },\n                \"cluster\": {\n                    \"#5E5CE6\": userColor\n                },\n                \"sentinel\": {\n                    \"#DC423C\": userColor\n                },\n                \"database\": {\n                    \"#DC423C\": userColor\n                }\n            }\n        }\n    }\n\n    MouseArea {\n        id: dragArea\n        anchors.fill: parent\n\n        acceptedButtons: root.sortConnections ? Qt.LeftButton : Qt.RightButton | Qt.MiddleButton\n\n        drag.target: root.sortConnections ? wrapper : null\n        drag.axis: Drag.YAxis\n\n        property bool held: false\n\n        hoverEnabled: true\n        propagateComposedEvents: !root.sortConnections\n\n        onReleased: {\n            if (root.sortConnections) {\n                wrapper.Drag.drop()\n                held = false\n                wrapper.color = \"transparent\"\n                wrapper.border.width = 0\n                return\n            }\n        }\n\n        onPressed: {\n            if (root.sortConnections && styleData.value[\"type\"] === \"server\") {\n                held = true\n                wrapper.border.width = 1\n                wrapper.border.color = sysPalette.light\n                return\n            }\n        }\n\n        onClicked: {\n            console.log(\"Catch event to item\")\n\n            if (mouse.button === Qt.RightButton) {\n                mouse.accepted = true\n                connectionTreeSelectionModel.setCurrentIndex(styleData.index, 1)\n                connectionsManager.sendEvent(styleData.index, \"right-click\")\n                return\n            }\n\n            if (mouse.button === Qt.MiddleButton) {\n                mouse.accepted = true\n                connectionsManager.sendEvent(styleData.index, \"mid-click\")\n                return\n            }\n        }\n\n        Rectangle {\n            id: wrapper\n            objectName: \"rdm_tree_view_item\"\n            height: PlatformUtils.isOSXRetina(Screen) ? 20 : 30\n            anchors.left: parent.left\n            anchors.right: parent.right\n            anchors.verticalCenter: parent.verticalCenter\n            anchors.rightMargin: 10\n            color: \"transparent\"\n\n            property var itemIndex: styleData.index\n\n            Drag.active: dragArea.held\n            Drag.hotSpot.x: width / 3\n            Drag.hotSpot.y: height / 3\n\n            states: State {\n                when: dragArea.held\n\n                ParentChange {\n                    target: wrapper\n                    parent: treeRoot\n                }\n                PropertyChanges {\n                    target: wrapper\n                    anchors.leftMargin: 30\n                }\n                AnchorChanges {\n                    target: wrapper\n                    anchors {\n                        verticalCenter: undefined\n                    }\n                }\n            }\n\n            property bool itemEnabled: styleData.value[\"state\"] === true\n            property bool itemLocked: styleData.value[\"locked\"] === true\n            property string itemType: styleData.value[\"type\"] ? styleData.value[\"type\"] : \"\"\n            property string userColor: styleData.value[\"user_color\"] ? styleData.value[\"user_color\"] : \"\"\n            property string itemIconSource: {\n                if (itemLocked) {\n                    return PlatformUtils.getThemeIcon(\"wait.svg\")\n                }\n\n                var type = itemType\n\n                if (type === \"server\") {\n                    var server_type = styleData.value[\"server_type\"]\n                    var serverIcon = \"\"\n\n                    if (server_type === \"unknown\") {\n                        serverIcon = PlatformUtils.getThemeIcon(\n                                    \"server_offline.svg\")\n                    } else if (server_type === \"standalone\") {\n                        serverIcon = PlatformUtils.getThemeIcon(\"server.svg\")\n                    } else {\n                        serverIcon = PlatformUtils.getThemeIcon(\n                                    server_type + \".svg\")\n                    }\n\n                    if (userColor) {\n                        return qmlUtils.replaceColorsInSvg(\n                                    serverIcon, root.__getIconColorMappings(\n                                        userColor)[server_type])\n                    } else {\n                        return serverIcon\n                    }\n                } else if (type === \"database\") {\n                    if (styleData.value[\"live_update\"] === true) {\n                        return PlatformUtils.getThemeIcon(\"live_update.svg\")\n                    } else {\n                        var icon = PlatformUtils.getThemeIcon(type + \".svg\")\n                        if (userColor) {\n                            return qmlUtils.replaceColorsInSvg(\n                                        icon, root.__getIconColorMappings(\n                                            userColor)[type])\n                        } else {\n                            return icon\n                        }\n                    }\n                } else if (type === \"namespace\" || type == \"server_group\"\n                           && styleData.isExpanded) {\n                    return PlatformUtils.getThemeIcon(type + \"_open.svg\")\n                } else {\n                    if (type !== \"\") {\n                        return PlatformUtils.getThemeIcon(type + \".svg\")\n                    } else {\n                        return \"\"\n                    }\n                }\n            }\n\n            Image {\n                id: itemIcon\n                anchors.left: parent.left\n                anchors.verticalCenter: parent.verticalCenter\n                sourceSize.width: 25\n                sourceSize.height: 25\n                source: wrapper ? wrapper.itemIconSource : \"\"\n                cache: true\n                asynchronous: true\n            }\n\n            Text {\n                objectName: \"rdm_tree_view_item_text\"\n                anchors.left: itemIcon.right\n                anchors.leftMargin: 3\n                anchors.verticalCenter: parent.verticalCenter\n                text: wrapper.itemEnabled ? styleData.value[\"name\"] : styleData.value[\"name\"]\n                                            + qsTranslate(\"RESP\", \" (Removed)\")\n                color: wrapper.itemEnabled ? styleData.selected ? sysPalette.highlightedText : sysPalette.windowText : inactiveSysPalette.windowText\n            }\n\n            Rectangle {\n                id: menuWrapper\n                implicitWidth: styleData.value[\"type\"] === \"database\"? 200 : 150\n                anchors {\n                    right: wrapper.right\n                    top: wrapper.top\n                    bottom: wrapper.bottom\n                    rightMargin: 20\n                }\n                height: parent.height\n                visible: styleData.selected && wrapper.itemEnabled\n                color: {\n                    var baseColor = sysPalette.highlight;\n                    if (styleData.selected) {\n                        return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.85)\n                    } else {\n                        return \"transparent\"\n                    }\n                }\n\n                Loader {\n                    id: menuLoader\n                    anchors {\n                        right: menuWrapper.right\n                        top: menuWrapper.top\n                        bottom: menuWrapper.bottom\n                    }\n                    height: parent.height\n                    asynchronous: false\n\n                    source: {\n                        if (!(styleData.selected && styleData.value[\"type\"]))\n                            return \"\"\n\n                        return \"./menu/\" + styleData.value[\"type\"] + \".qml\"\n                    }\n\n                    onLoaded: {\n                        wrapper.forceActiveFocus()\n                        menuWrapper.width = item.width\n                    }\n                }\n            }\n\n            focus: true\n            Keys.forwardTo: menuLoader.item ? [menuLoader.item] : []\n        }\n\n        DropArea {\n            anchors {\n                fill: parent\n            }\n\n            onEntered: {\n                if (styleData.value[\"type\"] === \"server_group\") {\n                    wrapper.border.width = 1\n                    wrapper.border.color = sysPalette.highlight\n                }\n            }\n\n            onDropped: {\n                wrapper.border.width = 0\n                connectionsManager.dropItemAt(drag.source.itemIndex,\n                                              styleData.index)\n            }\n\n            onExited: {\n                wrapper.border.width = 0\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections-tree/menu/InlineMenu.qml",
    "content": "import QtQuick 2.5\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.4\nimport QtQuick.Window 2.2\nimport \"./../../common/platformutils.js\" as PlatformUtils\nimport \"./../../\"\nimport \"./../../common/\"\n\nRowLayout {\n   id: root\n   property alias model: repeater.model   \n   property var callbacks\n\n   function sendEvent(e) {\n       if (!connectionsManager)\n           return\n\n       connectionsManager.sendEvent(styleData.index, e)\n   }\n\n   function callCallback(c) {\n       return callbacks[c]()\n   }\n\n   Repeater {\n        id: repeater\n\n        Item {\n            Layout.preferredWidth: PlatformUtils.isOSXRetina(Screen)? 20 : 25\n            Layout.preferredHeight: PlatformUtils.isOSXRetina(Screen)? 20 : 25\n            Layout.maximumHeight: PlatformUtils.isOSXRetina(Screen)? 20 : 25\n\n            ImageButton {\n                id: actionButton\n                anchors.fill: parent\n\n                iconSource: modelData['icon']\n                imgWidth: PlatformUtils.isOSXRetina(Screen)? 20 : 25\n                imgHeight: PlatformUtils.isOSXRetina(Screen)? 20 : 25\n\n                onClicked: handleClick()\n\n                function handleClick() {\n                    if (modelData['callback'] != undefined)\n                        return root.callCallback(modelData['callback'])\n                    else\n                        return root.sendEvent(modelData['event'])\n                }\n\n                tooltip: modelData['help'] != undefined ? modelData['help'] + (modelData[\"shortcut\"]? \" (\" + shortcut.nativeText + \")\" : \"\")  : \"\"\n\n                objectName: {\n                    if (modelData['event'] != undefined)\n                        return \"rdm_inline_menu_button_\" + modelData['event']\n\n                    if (modelData['callback'] != undefined)\n                        return \"rdm_inline_menu_button_\" + modelData['callback']\n\n                     return \"\"\n                }\n            }\n\n            Shortcut {\n                id: shortcut\n                sequence: modelData[\"shortcut\"]\n                onActivated: actionButton.handleClick()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections-tree/menu/database.qml",
    "content": "import QtQuick 2.5\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Window 2.2\nimport \"./../../common/platformutils.js\" as PlatformUtils\nimport \".\"\nimport \"./../../common/\"\n\nRowLayout {\n    id: root\n\n    focus: true\n    spacing: 0\n\n    state: \"menu\"\n\n    states: [\n        State {\n            name: \"menu\"\n            PropertyChanges { target: dbMenu; visible: true;}\n            PropertyChanges { target: bulkMenu; visible: false;}\n            PropertyChanges { target: filterMenu; visible: false;}            \n        },\n        State {\n            name: \"bulk_menu\"\n            PropertyChanges { target: dbMenu; visible: false;}\n            PropertyChanges { target: bulkMenu; visible: true;}\n            PropertyChanges { target: filterMenu; visible: false;}\n        },\n        State {\n            name: \"filter\"\n            PropertyChanges { target: dbMenu; visible: false;}\n            PropertyChanges { target: bulkMenu; visible: false;}\n            PropertyChanges { target: filterMenu; visible: true;}\n        }\n    ]\n\n    Keys.onPressed: {\n        if (state == \"filter\" && event.key == Qt.Key_Escape) {\n            state = \"menu\"\n        }\n    }\n\n    InlineMenu {\n        id: dbMenu\n\n        Layout.fillWidth: true\n\n        callbacks: {\n            \"filter\": function() {\n                root.state = \"filter\"\n                filterCombobox.currentIndex = filterCombobox.find(styleData.value[\"filter\"])\n                filterCombobox.editText = styleData.value[\"filter\"]\n            },\n            \"live_update\": function () {\n                if (styleData.value[\"live_update\"]) {\n                    connectionsManager.setMetadata(styleData.index, \"live_update\", '')\n                } else {\n                    connectionsManager.setMetadata(styleData.index, \"live_update\", true)\n                }\n            },\n            \"bulk_menu\": function() {\n                root.state = \"bulk_menu\"\n            },\n        }\n\n        model: {\n            if (styleData.value[\"locked\"] === true) {\n                return [\n                            {\n                                'icon': PlatformUtils.getThemeIcon(\"offline.svg\"), 'event': 'cancel', \"help\": qsTranslate(\"RESP\",\"Disconnect\"),\n                            },\n                        ]\n            } else {\n                return [\n                            {\n                                'icon': PlatformUtils.getThemeIcon(\"filter.svg\"), 'callback': 'filter', \"help\": qsTranslate(\"RESP\",\"Open Keys Filter\"),\n                                \"shortcut\": \"Ctrl+F\",\n                            },\n                            {\n                                'icon': PlatformUtils.getThemeIcon(\"refresh.svg\"), 'event': 'reload', \"help\": qsTranslate(\"RESP\",\"Reload Keys in Database\"),\n                                \"shortcut\": \"Ctrl+R\",\n                            },\n                            {\n                                'icon': PlatformUtils.getThemeIcon(\"add.svg\"), 'event': 'add_key', \"help\": qsTranslate(\"RESP\",\"Add New Key\"),\n                                \"shortcut\": \"Ctrl+N\",\n                            },\n                            {\n                                'icon': styleData.value[\"live_update\"]? PlatformUtils.getThemeIcon(\"live_update_disable.svg\") : PlatformUtils.getThemeIcon(\"live_update.svg\"),\n                                'callback': 'live_update',\n                                \"help\": styleData.value[\"live_update\"]? qsTranslate(\"RESP\",\"Disable Live Update\") : qsTranslate(\"RESP\",\"Enable Live Update\"),\n                                \"shortcut\": \"Ctrl+L\",\n                            },\n                            {\n                                'icon': PlatformUtils.getThemeIcon(\"console.svg\"), 'event': 'console', \"help\": qsTranslate(\"RESP\",\"Open Console\"),\n                                \"shortcut\": \"Ctrl+T\",\n                            },\n                            {'icon': PlatformUtils.getThemeIcon(\"memory_usage.svg\"), \"event\": \"analyze_memory_usage\", \"help\": qsTranslate(\"RESP\",\"Analyze Used Memory\")},\n                            {\n                                'icon': PlatformUtils.getThemeIcon(\"bulk_operations.svg\"), 'callback': 'bulk_menu', \"help\": qsTranslate(\"RESP\",\"Bulk Operations\"),\n                            },\n                        ]\n            }\n        }\n    }\n\n    InlineMenu {\n        id: bulkMenu\n\n        Layout.fillWidth: true\n\n        callbacks: {\n            \"db_menu\": function() {\n                root.state = \"menu\"\n            },\n        }\n\n        model: {\n            return [\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"cleanup.svg\"), 'event': 'flush', \"help\": qsTranslate(\"RESP\",\"Flush Database\"),\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"cleanup_filtered.svg\"), 'event': 'delete_keys', \"help\": qsTranslate(\"RESP\",\"Delete keys with filter\"),\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"ttl.svg\"), 'event': 'ttl', \"help\": qsTranslate(\"RESP\",\"Set TTL for multiple keys\"),\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"db-copy.svg\"), 'event': 'copy_keys', \"help\": qsTranslate(\"RESP\",\"Copy keys from this database to another\"),\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"import.svg\"), 'event': 'rdb_import', \"help\": qsTranslate(\"RESP\",\"Import keys from RDB file\"),\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"back.svg\"), 'callback': 'db_menu', \"help\": qsTranslate(\"RESP\",\"Back\"),\n                        },\n\n                    ]\n        }\n\n    }\n\n    RowLayout {\n        id: filterMenu\n\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n        Layout.topMargin: PlatformUtils.isOSX() ? -3 : 0\n\n        property int btnWidth: PlatformUtils.isOSXRetina(Screen)? 18 : 22\n        property int btnHeight: PlatformUtils.isOSXRetina(Screen)? 18 : 22\n\n        BetterComboBox {\n            id: filterCombobox\n            objectName: \"rdm_inline_menu_filter_field\"\n            editable: true\n\n            Layout.preferredWidth: connectionsTree.width * 0.4\n            Layout.preferredHeight: PlatformUtils.isOSX()? 25 : 30\n\n            indicator.width: PlatformUtils.isOSX()? 30 : 40\n            indicator.height: PlatformUtils.isOSX()? 25 : 30\n\n            selectTextByMouse: true\n            editText: styleData.value[\"filter\"]\n            model: styleData.value[\"filterHistory\"]\n\n            palette.highlightedText: sysPalette.highlightedText\n\n            delegate: ItemDelegate {\n                height: filterCombobox.height\n                width: filterCombobox.width\n                highlighted: filterCombobox.highlightedIndex === index\n                contentItem: Text {\n                    text: modelData\n                    color: parent.highlighted ? sysPalette.buttonText : sysPalette.text\n                    verticalAlignment: Text.AlignVCenter\n                    elide: Text.ElideRight\n\n                    BetterToolTip {\n                        title: modelData\n                        visible: parent.truncated && title && hovered\n                    }\n                }\n            }\n\n            onAccepted: {\n                filterOk.setFilter()\n                focus = false\n            }\n        }\n\n        ImageButton {\n            id: filterOk\n\n            implicitWidth: filterMenu.btnWidth\n            implicitHeight: filterMenu.btnHeight\n            imgWidth: filterMenu.btnWidth\n            imgHeight: filterMenu.btnHeight\n            iconSource: PlatformUtils.getThemeIcon(\"ok.svg\")\n            objectName: \"rdm_inline_menu_button_apply_filter\"\n\n            onClicked: setFilter()\n\n            function setFilter() {\n                if (!connectionsManager)\n                    return\n\n                connectionsManager.setMetadata(styleData.index, \"filter\", filterCombobox.editText)\n                root.state = \"menu\"\n            }\n        }\n\n        ImageButton {\n            id: filterHelp\n\n            implicitWidth: filterMenu.btnWidth\n            implicitHeight: filterMenu.btnHeight\n            imgWidth: filterMenu.btnWidth\n            imgHeight: filterMenu.btnHeight\n            iconSource: PlatformUtils.getThemeIcon(\"help.svg\")\n            onClicked: Qt.openUrlExternally(\"https://docs.resp.app/en/latest/lg-keyspaces/#use-specific-scan-filter-to-reduce-loaded-amount-of-keys\")\n        }\n\n        ImageButton {\n            id: filterCancel\n\n            implicitWidth: filterMenu.btnWidth\n            implicitHeight: filterMenu.btnHeight\n            imgWidth: filterMenu.btnWidth\n            imgHeight: filterMenu.btnHeight\n            iconSource: PlatformUtils.getThemeIcon(\"clear.svg\")\n            objectName: \"rdm_inline_menu_button_reset_filter\"\n\n            onClicked: {\n                if (!connectionsManager)\n                    return\n\n                connectionsManager.setMetadata(styleData.index, \"filter\", \"\")\n                root.state = \"menu\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections-tree/menu/key.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.4\nimport \".\"\nimport \"./../../common/platformutils.js\" as PlatformUtils\n\nInlineMenu {\n    id: root\n\n    callbacks: {\n        \"copy\": function() {\n            var result = styleData.value[\"full_name\"]\n\n            if (result) {\n                qmlUtils.copyToClipboard(result)\n            }\n        },\n    }\n\n    model:\n        [\n            {'icon': PlatformUtils.getThemeIcon(\"copy.svg\"), \"callback\": \"copy\", \"help\": qsTranslate(\"RESP\",\"Copy Key Name\"), \"shortcut\": \"Ctrl+C\"},\n            {'icon': PlatformUtils.getThemeIcon(\"delete.svg\"), \"event\": \"delete\", \"help\": qsTranslate(\"RESP\",\"Delete key\"), \"shortcut\": \"D\"}\n        ]\n}\n"
  },
  {
    "path": "src/qml/connections-tree/menu/namespace.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.4\nimport \".\"\nimport \"./../../common/platformutils.js\" as PlatformUtils\n\nInlineMenu {\n    id: root\n\n    callbacks: {\n        \"copy\": function() {\n            var result = styleData.value[\"full_path\"]\n\n            if (result) {\n                qmlUtils.copyToClipboard(result + \":*\")\n            }\n        },\n    }\n\n    model: {\n        if (styleData.value[\"locked\"] === true) {\n            return [\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"offline.svg\"), 'event': 'cancel', \"help\": qsTranslate(\"RESP\",\"Disconnect\"),\n                        },\n                    ]\n        } else {\n            [\n                {'icon': PlatformUtils.getThemeIcon(\"refresh.svg\"), \"event\": \"reload\", \"help\": qsTranslate(\"RESP\",\"Reload Namespace\"), \"shortcut\": \"Ctrl+R\"},\n                {'icon': PlatformUtils.getThemeIcon(\"add.svg\"), 'event': 'add_key', \"help\": qsTranslate(\"RESP\",\"Add New Key\")},\n                {'icon': PlatformUtils.getThemeIcon(\"copy.svg\"), \"callback\": \"copy\", \"help\": qsTranslate(\"RESP\",\"Copy Namespace Pattern\"), \"shortcut\": \"Ctrl+C\"},\n                {'icon': PlatformUtils.getThemeIcon(\"memory_usage.svg\"), \"event\": \"analyze_memory_usage\", \"help\": qsTranslate(\"RESP\",\"Analyze Used Memory\")},\n                {'icon': PlatformUtils.getThemeIcon(\"delete.svg\"), \"event\": \"delete\", \"help\": qsTranslate(\"RESP\",\"Delete Namespace\"), \"shortcut\": \"D\"},\n            ]\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/qml/connections-tree/menu/server.qml",
    "content": "import QtQuick 2.5\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.4\nimport \".\"\nimport \"./../../common/platformutils.js\" as PlatformUtils\n\nInlineMenu {\n    id: root\n\n    model: {\n        if (styleData.value[\"locked\"] === true) {\n            return [\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"offline.svg\"), 'event': 'cancel', \"help\": qsTranslate(\"RESP\",\"Disconnect\"),\n                        },\n                    ]\n        } else {\n            return [\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"refresh.svg\"), 'event': 'reload', \"help\": qsTranslate(\"RESP\",\"Reload Server\"),\n                            \"shortcut\": \"Ctrl+R\",\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"offline.svg\"), 'event': 'unload', \"help\": qsTranslate(\"RESP\",\"Unload All Data\"),\n                            \"shortcut\": \"Ctrl+U\",\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"settings.svg\"), 'event': 'edit', \"help\": qsTranslate(\"RESP\",\"Edit Connection Settings\"),\n                            \"shortcut\": \"Ctrl+E\",\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"copy.svg\"), 'event': 'duplicate', \"help\": qsTranslate(\"RESP\",\"Duplicate Connection\"),\n                            \"shortcut\": \"Ctrl+C\",\n                        },\n                        {\n                            'icon': PlatformUtils.getThemeIcon(\"delete.svg\"), 'event': 'delete', \"help\": qsTranslate(\"RESP\",\"Delete Connection\"),\n                            \"shortcut\": \"D\",\n                        },\n                    ]\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/connections-tree/menu/server_group.qml",
    "content": "import QtQuick 2.5\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.4\nimport \".\"\nimport \"./../../common/platformutils.js\" as PlatformUtils\n\nInlineMenu {\n    id: root\n\n    model: {\n        return [\n                    {\n                        'icon': PlatformUtils.getThemeIcon(\"settings.svg\"), 'event': 'edit', \"help\": qsTranslate(\"RESP\",\"Edit Connection Group\"),\n                        \"shortcut\": \"Ctrl+E\",\n                    },\n                    {\n                        'icon': PlatformUtils.getThemeIcon(\"delete.svg\"), 'event': 'delete', \"help\": qsTranslate(\"RESP\",\"Delete Connection Group\"),\n                        \"shortcut\": \"D\",\n                    },\n                ]\n    }\n}\n"
  },
  {
    "path": "src/qml/console/BaseConsole.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 1.4\n\nTextArea {\n    id: root\n\n    function clear() {\n        text = \"\"\n    }\n\n    wrapMode: TextEdit.WrapAnywhere\n    textFormat: TextEdit.PlainText\n}\n"
  },
  {
    "path": "src/qml/console/Consoles.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.3\nimport QtQuick.Controls.Styles 1.1\nimport QtQuick.Window 2.2\nimport \"./../common\"\nimport \"./../common/platformutils.js\" as PlatformUtils\n\nRepeater {\n    id: root\n\n    BetterTab {\n        id: tab\n        objectName: \"rdm_console_tab\"\n\n        Component {\n            id: consoleTabButton\n\n            BetterTabButton {\n                icon.source: PlatformUtils.getThemeIcon(\"console.svg\")\n\n                text: tabName\n                tooltip: tabName\n\n                onCloseClicked: {\n                    consoleModel.closeTab(tabIndex)\n                }\n            }\n        }\n\n        Component.onCompleted: {           \n            var tabButton = consoleTabButton.createObject(tab);\n            tabButton.self = tabButton;\n            tabButton.tabRef = tab;\n            tabBar.addItem(tabButton)\n            tabBar.activateTabButton(tabButton)\n            tabs.activateTab(tab)\n\n            tabModel.init()\n        }\n\n        RedisConsole {\n            id: redisConsole            \n\n            anchors.fill: parent\n\n            Connections {\n                target: tabModel\n\n                function onChangePrompt(text, showPrompt) { redisConsole.setPrompt(text, showPrompt) }\n                function onAddOutput(text, resultType) { redisConsole.addOutput(text, resultType) }\n            }\n\n            onExecCommand: tabModel.executeCommand(command)\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/console/RedisConsole.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Controls 1.4\nimport QtQuick.Layouts 1.1\nimport \"../common\"\nimport \"../common/platformutils.js\" as PlatformUtils\nimport \".\"\nimport rdm.models 1.0\n\nRectangle {\n    id: root\n    color: \"#3A3A3A\"\n\n    property bool cursorInEditArea: false\n    property string prompt\n    property int promptPosition\n    property int promptLength: prompt.length\n    property alias busy: textArea.readOnly\n\n    property string initText:\n          \"<span style='color: white; font-size: 13pt;'>RESP.app Redis Console</span><br/>\" +\n          qsTranslate(\"RESP\",\"Connecting...\")\n\n\n    function setPrompt(txt, display) {\n        console.log(\"set prompt: \", txt, display)\n        prompt = txt\n\n        if (display)\n            displayPrompt();\n    }\n\n    function displayPrompt() {\n        textArea.insert(textArea.length, prompt)\n        promptPosition = textArea.length - promptLength\n        //textArea.cursorPosition = textArea.length - 1\n    }\n\n    function clear() {\n        textArea.clear()\n    }\n\n    function addOutput(text, type) {\n\n        if (type == \"error\") {\n            textArea.append(\"<span style='color: red; font-family: \"\n                            + appSettings.valueEditorFont + \"'>\"\n                            + qmlUtils.escapeHtmlEntities(text) + '</span>')\n        } else {            \n            textArea.append(\"<code style='color: white;white-space: pre-wrap;font-family: \"\n                            + appSettings.valueEditorFont + \"'>\"\n                            + qmlUtils.escapeHtmlEntities(text) + '</code>')\n        }\n\n        if (type == \"complete\" || type == \"error\") {\n            textArea.blockAllInput = false\n            textArea.append(\"<br/>\")\n            displayPrompt()\n        }\n    }\n\n    signal execCommand(string command)\n\n    BaseConsole {\n        id: textArea\n        anchors.fill: parent\n        backgroundVisible: false\n        textColor: \"yellow\"\n        readOnly: root.promptLength == 0 || blockAllInput\n        textFormat: TextEdit.RichText\n        menu: null\n\n        property bool blockAllInput: false\n        property int commandStartPos: root.promptPosition + root.promptLength\n\n        function getCurrentCommand() {\n            return getText(commandStartPos, length)\n        }\n\n        Keys.onPressed: {\n            if (readOnly) {\n                console.log(\"Console is read-only. Ignore Key presses.\")\n                return\n            }\n\n            var cursorInReadOnlyArea = cursorPosition < commandStartPos\n\n            if (event.key == Qt.Key_Backspace && cursorPosition <= commandStartPos) {\n                event.accepted = true\n                console.log(\"Block backspace\")\n                return\n            }\n\n            if (event.key == Qt.Key_Left && cursorPosition <= commandStartPos) {\n                event.accepted = true\n                console.log(\"Block left arrow\")\n                return\n            }\n\n            if (((event.modifiers == Qt.NoModifier) || (event.modifiers & Qt.ShiftModifier))\n                    && cursorInReadOnlyArea) {\n                cursorPosition = length\n                event.accepted = true\n                console.log(\"Block Input in Read-Only area\")\n                return\n            }\n\n            if (event.matches(StandardKey.Undo)\n                    && cursorPosition == commandStartPos) {\n                event.accepted = true\n                console.log(\"Block Undo\")\n                return\n            }\n\n            if (selectionStart < commandStartPos\n                    && (event.matches(StandardKey.Cut)\n                        || event.key == Qt.Key_Delete\n                        || event.key == Qt.Key_Backspace)) {\n                event.accepted = true\n                console.log(\"Block Cut/Delete\")\n                return\n            }\n\n            if (event.matches(StandardKey.Paste)) {\n                event.accepted = true\n                console.log(\"Block Reach Text Input\")\n                hiddenBuffer.text = \"\"\n                hiddenBuffer.paste()\n\n                if (cursorInReadOnlyArea)\n                    cursorPosition = length\n\n                insert(cursorPosition, hiddenBuffer.text.trim())\n\n                return\n            }\n\n            if (event.key == Qt.Key_Up || event.key == Qt.Key_Down) {\n                var command;\n\n                if (commandsHistoryModel.historyNavigation) {\n                    if (event.key == Qt.Key_Down) {\n                        command = commandsHistoryModel.getNextCommand()\n                    } else {\n                        command = commandsHistoryModel.getPrevCommand()\n                    }\n                } else {\n                    command = commandsHistoryModel.getCurrentCommand()\n                }\n\n                remove(commandStartPos, length)\n                insert(commandStartPos, command)\n\n                event.accepted = true\n                return\n            }\n\n            if (event.key == Qt.Key_Return && cursorPosition > commandStartPos) {\n                var command = getText(commandStartPos, length)\n                blockAllInput = true\n                event.accepted = true\n\n                if (command.toLowerCase() === \"clear\") {\n                    root.clear()\n                    root.displayPrompt()\n                    blockAllInput = false\n                } else {\n                    root.execCommand(command)\n                }\n\n                commandsHistoryModel.appendCommand(command)\n            }\n\n            autocompleteModel.filterString = \"^\" + getText(commandStartPos, cursorPosition) + event.text\n        }\n\n        Component.onCompleted: {\n            textArea.text = root.initText\n        }\n\n        MouseArea {\n            anchors.fill: parent\n            acceptedButtons: Qt.RightButton\n\n            onClicked: {\n                menu.popup()\n            }\n        }\n\n        Menu {\n            id: menu\n\n            MenuItem {\n                text: qsTranslate(\"RESP\",\"Clear\")\n                iconSource: PlatformUtils.getThemeIcon(\"cleanup.svg\")\n                onTriggered: {\n                    root.clear()\n                    root.displayPrompt()\n                }\n            }\n        }\n    }\n\n    ColumnLayout {\n        objectName: \"rdm_autocomplete_results\"\n        height: 150\n        width: root.width - x - 50\n\n        x: textArea.cursorRectangle? textArea.cursorRectangle.x : 0\n        y: textArea.cursorRectangle? textArea.cursorRectangle.y + 20 : 0\n        z: 255\n        visible: {\n            return cmdAutocomplete.rowCount > 0\n                    && autocompleteModel.filterString.length > 0\n                    && textArea.cursorPosition >= textArea.commandStartPos\n        }\n\n        TableView {\n            id: cmdAutocomplete            \n\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n\n            model: autocompleteModel\n\n            headerVisible: true\n\n            TableViewColumn {\n                title: \"Command\"\n                role: \"name\"\n                width: 120\n            }\n\n            TableViewColumn {\n                title: qsTranslate(\"RESP\",\"Arguments\")\n                role: \"arguments\"\n                width: 250\n            }\n\n            TableViewColumn {\n                title: qsTranslate(\"RESP\",\"Description\")\n                role: \"summary\"\n                width: 350\n            }\n\n            TableViewColumn {\n                title: qsTranslate(\"RESP\",\"Available since\")\n                role: \"since\"\n                width: 60\n            }\n\n            itemDelegate: Item {\n                Text {\n                    anchors.fill: parent\n                    color: styleData.textColor\n                    elide: styleData.elideMode\n                    text: styleData.value\n                    wrapMode: Text.WrapAnywhere\n                    maximumLineCount: 1\n                }\n\n                MouseArea {\n                    enabled: styleData.column === 2 || styleData.column === 0\n                    anchors.fill: parent\n                    cursorShape: Qt.PointingHandCursor\n                    onClicked: {\n                        var commandName = \"#\"\n                        try {\n                            commandName = consoleAutocompleteModel.getRow(\n                                        autocompleteModel.getOriginalRowIndex(styleData.row)\n                            )[\"name\"]\n                        } catch(err) {\n                            console.log(\"Cannot get command name:\", err)\n                        }\n\n                        if (styleData.column === 2) {\n                            Qt.openUrlExternally(\"https://redis.io/commands/\" + commandName)\n                        } else {\n                            textArea.remove(textArea.commandStartPos, textArea.cursorPosition)\n                            textArea.insert(textArea.commandStartPos, commandName)\n                            autocompleteModel.filterString = commandName\n                        }\n                    }\n                }\n            }\n        }\n\n        RowLayout {\n            Layout.minimumWidth: 150\n            Layout.minimumHeight: closeBtn.implicitHeight\n\n            Item {\n                Layout.fillWidth: true\n            }\n\n            Button {\n                id: closeBtn\n                text: qsTranslate(\"RESP\",\"Close\")\n                onClicked: {\n                    autocompleteModel.filterString = \"\"\n                }\n            }\n        }\n    }\n\n    SortFilterProxyModel {\n        id: autocompleteModel\n        source: consoleAutocompleteModel\n\n        filterSyntax: SortFilterProxyModel.RegExp\n        filterCaseSensitivity: Qt.CaseInsensitive\n        filterRole: \"name\"\n    }\n\n    TextArea {\n        id: hiddenBuffer\n        visible: false\n        textFormat: TextEdit.PlainText\n    }\n\n    ListModel {\n        id: commandsHistoryModel\n        property int currentIndex: 0\n        property bool historyNavigation: false\n\n        function getCurrentCommand() {\n            checkCurrentPos()\n            var res = get(currentIndex)\n            historyNavigation = true\n            return res[\"cmd\"]\n        }\n\n        function getNextCommand() {\n            currentIndex += 1\n            return getCurrentCommand()\n        }\n\n        function getPrevCommand() {\n            currentIndex -= 1\n            return getCurrentCommand()\n        }\n\n        function checkCurrentPos() {\n            if (currentIndex >= count)\n                currentIndex = commandsHistoryModel.count - 1\n\n            if (currentIndex < 0)\n                currentIndex = 0\n        }\n\n        function appendCommand(cmdStr) {\n            append({\"cmd\": cmdStr})\n            currentIndex = count - 1\n            historyNavigation = false\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/dummy.qml",
    "content": "// Unused dummy QML file to specify import dependencies\n// This isn't included in the build, but is read by qmlimportscanner for builds.\n\nimport QtQuick 2.2\nimport QtQuick.PrivateWidgets 1.1\n\nItem { }\n"
  },
  {
    "path": "src/qml/extension-server/ExtensionServerSettings.qml",
    "content": "import QtQuick 2.15\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport Qt.labs.settings 1.0\nimport QtQuick.Window 2.3\nimport \"../common\"\nimport \"../settings\"\nimport \"../common/platformutils.js\" as PlatformUtils\n\nBetterDialog {\n    id: root\n    title: qsTranslate(\"RESP\",\"Extension Server\")\n\n    footer: null\n\n    property bool restartRequired: false\n\n    contentItem: Rectangle {\n        id: dialogRoot\n        implicitWidth: 950\n        implicitHeight: 550\n\n        color: sysPalette.base\n\n        Control {\n            palette: approot.palette\n            anchors.fill: parent\n            anchors.margins: 20\n\n            ScrollView {\n                id: globalSettingsScrollView\n                width: parent.width\n                height: parent.height\n                ScrollBar.horizontal.policy: ScrollBar.AlwaysOff\n\n                ColumnLayout {\n                    id: innerLayout\n                    width: globalSettingsScrollView.width - 25\n                    height: (dialogRoot.height - 50 > implicitHeight) ? dialogRoot.height - 50 : implicitHeight\n                    spacing: 10\n\n                    RowLayout {\n                        Layout.fillWidth: true\n\n                        SettingsGroupTitle {\n                            Layout.fillWidth: true\n                            text: qsTranslate(\"RESP\",\"Connection Settings\")\n                        }\n                    }\n\n                    ColumnLayout {\n                        Layout.fillWidth: true                        \n                        spacing: 10\n\n\n                        RowLayout {\n\n                            BetterLabel {\n                                Layout.preferredWidth: 200\n                                text: qsTranslate(\"RESP\",\"Server Url:\")\n                            }\n\n                            BetterTextField {\n                                id: serverUrl\n\n                                Layout.fillWidth: true\n                            }\n                        }\n\n                        RowLayout {\n\n                            BetterLabel {\n                                Layout.preferredWidth: 200\n                                text: qsTranslate(\"RESP\",\"Basic Auth:\")\n                            }\n\n                            BetterTextField {\n                                id: serverAuthTokenName\n                                Layout.fillWidth: true\n                                placeholderText: qsTranslate(\"RESP\",\"User\")\n                            }\n\n                            PasswordInput {\n                                id: serverAuthTokenValue\n                                Layout.fillWidth: true\n                                placeholderText: qsTranslate(\"RESP\",\"Password\")\n                            }\n                        }\n                        IntOption {\n                            id: responseTimeout\n\n                            Layout.preferredHeight: 30\n                            Layout.fillWidth: true\n\n                            min: 1\n                            max: 60\n                            value: 10\n                            label: qsTranslate(\"RESP\",\"Response timeout  (in seconds)\")\n                        }\n\n\n                    }                                       \n\n                    RowLayout {\n                        Layout.topMargin: 10\n\n                        SettingsGroupTitle {                            \n                            text: qsTranslate(\"RESP\", \"Available Data Formatters\")\n                        }\n\n                        Item {\n                            Layout.fillWidth: true\n                        }\n\n                        BetterButton {\n                            text: qsTranslate(\"RESP\", \"Reload\")\n                            onClicked: {\n                                formattersManager.loadFormatters();\n                            }\n                        }\n                    }\n\n                    LC.TableView {\n                        id: formattersTable\n\n                        Layout.fillWidth: true\n                        Layout.fillHeight: true\n                        Layout.preferredHeight: 100\n                        verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn\n\n                        LC.TableViewColumn {\n                            role: \"id\"\n                            width: 75\n                            title: qsTranslate(\"RESP\",\"Id\")\n                        }\n\n                        LC.TableViewColumn {\n                            width: 250\n                            role: \"name\"\n                            title: qsTranslate(\"RESP\",\"Name\")\n                        }\n\n                        LC.TableViewColumn {\n                            width: 75\n                            role: \"readOnly\"\n                            title: qsTranslate(\"RESP\",\"Read Only\")\n                        }\n\n                        model: formattersManager\n                    }\n\n                    Item {\n                        visible: !formattersTable.visible\n                        Layout.fillHeight: true\n                    }\n\n                    RowLayout {\n                        Layout.fillWidth: true\n\n                        Item { Layout.fillWidth: true; }\n                        BetterButton {\n                            text: qsTranslate(\"RESP\",\"OK\")\n                            onClicked: {\n                                if (root.restartRequired === true) {\n                                    // restart app\n                                    Qt.exit(1001)\n                                }\n\n                                restartRequired = false\n                                root.close()\n                            }\n                        }\n                        BetterButton {\n                            text: qsTranslate(\"RESP\",\"Cancel\")\n                            onClicked: root.close()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Settings {\n        id: globalSettings\n        category: \"app\"       \n\n        property alias extensionServerUrl: serverUrl.text\n        property alias extensionServerUser: serverAuthTokenName.text\n        property alias extensionServerPassword: serverAuthTokenValue.text\n        property alias extensionServerRequestTimeout: responseTimeout.value\n\n    }\n\n    Component.onCompleted: {\n        restartRequired = false\n    }\n}\n"
  },
  {
    "path": "src/qml/qml.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file>app.qml</file>\n        <file>WelcomeTab.qml</file>\n        <file>QuickStartDialog.qml</file>\n        <file>common/RichTextWithLinks.qml</file>\n        <file>common/PasswordInput.qml</file>\n        <file>common/AddressInput.qml</file>\n        <file>common/FilePathInput.qml</file>\n        <file>common/BetterTabView.qml</file>\n        <file>common/BetterTab.qml</file>\n        <file>connections/ConnectionSettignsDialog.qml</file>\n        <file>connections-tree/menu/InlineMenu.qml</file>\n        <file>connections-tree/menu/server.qml</file>\n        <file>connections-tree/menu/database.qml</file>\n        <file>connections-tree/menu/namespace.qml</file>\n        <file>connections-tree/menu/key.qml</file>\n        <file>value-editor/AddKeyDialog.qml</file>\n        <file>value-editor/ValueTabs.qml</file>\n        <file>value-editor/Pagination.qml</file>\n        <file>value-editor/editors/HashItemEditor.qml</file>\n        <file>value-editor/editors/StreamItemEditor.qml</file>\n        <file>value-editor/editors/SingleItemEditor.qml</file>\n        <file>value-editor/editors/SortedSetItemEditor.qml</file>\n        <file>value-editor/editors/AbstractEditor.qml</file>\n        <file>value-editor/editors/MultilineEditor.qml</file>\n        <file>value-editor/editors/formatters/hexy.js</file>\n        <file>value-editor/editors/editor.js</file>\n        <file>console/RedisConsole.qml</file>\n        <file>console/BaseConsole.qml</file>\n        <file>console/Consoles.qml</file>\n        <file>connections-tree/BetterTreeView.qml</file>\n        <file>AppToolBar.qml</file>\n        <file>common/BetterSplitView.qml</file>\n        <file>common/ImageButton.qml</file>\n        <file>settings/GlobalSettings.qml</file>\n        <file>settings/BoolOption.qml</file>\n        <file>settings/FontSizeOption.qml</file>\n        <file>settings/IntOption.qml</file>\n        <file>bulk-operations/BulkOperationsDialog.qml</file>\n        <file>settings/ComboboxOption.qml</file>\n        <file>common/platformutils.js</file>\n        <file>common/NewTextArea.qml</file>\n        <file>common/BetterGroupbox.qml</file>\n        <file>common/BetterCheckbox.qml</file>\n        <file>common/BetterRadioButton.qml</file>\n        <file>common/BetterTextField.qml</file>\n        <file>common/SettingsGroupTitle.qml</file>\n        <file>common/BetterButton.qml</file>\n        <file>LogView.qml</file>\n        <file>value-editor/ValueTableCell.qml</file>\n        <file>common/BetterTabButton.qml</file>\n        <file>common/BetterMessageDialog.qml</file>\n        <file>common/BetterDialog.qml</file>\n        <file>common/OkDialog.qml</file>\n        <file>common/OkDialogOverlay.qml</file>\n        <file>common/BetterSpinBox.qml</file>\n        <file>common/BetterComboBox.qml</file>\n        <file>value-editor/editors/formatters/ValueFormatters.qml</file>\n        <file>common/FastTextView.qml</file>\n        <file>common/BetterDialogButtonBox.qml</file>\n        <file>common/BetterToolTip.qml</file>\n        <file>common/SaveToFileButton.qml</file>\n        <file>common/BetterLabel.qml</file>\n        <file>connections-tree/ConnectionGroupDialog.qml</file>\n        <file>connections-tree/menu/server_group.qml</file>\n        <file>value-editor/ValueTable.qml</file>\n        <file>value-editor/ValueTableActions.qml</file>\n        <file>value-editor/filters/ListFilters.qml</file>\n        <file>value-editor/filters/StreamFilters.qml</file>\n        <file>common/JsonHighlighter.qml</file>\n        <file>connections/AskSecretDialog.qml</file>\n        <file>common/ColorInput.qml</file>\n        <file>value-editor/editors/UnsupportedDataType.qml</file>\n        <file>value-editor/editors/ReadOnlySingleItemEditor.qml</file>\n        <file>extension-server/ExtensionServerSettings.qml</file>\n        <file>connections-tree/TreeItemDelegate.qml</file>\n        <file>server-actions/ServerActionTabs.qml</file>\n        <file>server-actions/ServerCharts.qml</file>\n        <file>server-actions/ServerConfig.qml</file>\n        <file>server-actions/ServerSlowlog.qml</file>\n        <file>server-actions/ServerClients.qml</file>\n        <file>server-actions/ServerPubSub.qml</file>\n        <file>server-actions/ServerAction.qml</file>\n        <file>common/LegacyTableView.qml</file>\n        <file>common/BetterMenu.qml</file>\n        <file>common/BetterMenuItem.qml</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "src/qml/server-actions/ServerAction.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.13\n\nItem {\n    id: root\n    property var model\n    property alias uiBlocked: uiBlocker.visible\n\n    function stopTimer() {}\n\n    Component.onCompleted: {\n        uiBlocker.visible = true\n    }\n\n    Rectangle {\n        id: uiBlocker\n        visible: false\n        anchors.fill: parent\n        color: Qt.rgba(sysPalette.base.red, sysPalette.base.green, sysPalette.base.blue, 0.15)\n        z: 1000\n\n        Item {\n            anchors.fill: parent\n\n            ProgressBar {\n                anchors.centerIn: parent\n                indeterminate: true\n            }\n        }\n\n        MouseArea {\n            anchors.fill: parent\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/server-actions/ServerActionTabs.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.13\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport QtQuick.Window 2.2\nimport QtCharts 2.3\nimport \"./../common\"\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../settings\"\n\n\nRepeater {\n    id: root\n\n    BetterTab {\n        id: serverTab\n\n        Component {\n            id: serverTabButton\n\n            BetterTabButton {\n                icon.source: PlatformUtils.getThemeIcon(\"database.svg\")\n\n                text: tabName\n\n                onCloseClicked: {\n                    serverStatsModel.closeTab(tabIndex)\n                }\n            }\n        }\n\n        Component.onCompleted: {\n            var tabButton = serverTabButton.createObject(serverTab);\n            tabButton.self = tabButton;\n            tabButton.tabRef = serverTab;\n            tabBar.addItem(tabButton)\n            tabBar.activateTabButton(tabButton)\n            tabs.activateTab(serverTab)\n        }\n\n        property var model: tabModel\n\n        onModelChanged: {\n            if (!model)\n                return;\n\n            serverTab.model.init()\n        }\n\n\n        function getValue(cat, prop) {\n            try {\n                return model.serverInfo[cat][prop]\n            } catch(e) {\n                console.error(\"Cannot get server info '\" + prop + \"' from \" + cat)\n                return \"\"\n            }\n        }\n\n        function getIntValue(cat, prop) {\n            var val = getValue(cat, prop)\n            if (val !== \"\") return parseInt(val)\n            return 0\n        }\n\n        function getHitRatio() {\n            var hits = getIntValue(\"stats\", \"keyspace_hits\")\n            var misses = getIntValue(\"stats\", \"keyspace_misses\")\n            var total = hits + misses\n            if (total === 0) {\n                return 0\n            }\n            return Math.round(hits / total * 100 * 100) / 100\n        }\n\n        Rectangle {\n            id: wrappingBackground\n            anchors.fill: parent\n\n            color: sysPalette.base\n\n            ColumnLayout {                \n                clip: true\n                anchors.fill: parent\n                anchors.margins: 15\n\n                Component {\n                    id: actionsMenu\n\n                    ColumnLayout {\n\n                        GridLayout {\n                            id: tileGrid\n                            columns: 4\n\n                            Layout.fillWidth: true\n\n                            property int tileSize: PlatformUtils.isScalingDisabled()? 150 : 110\n                            property int tileIconSize: PlatformUtils.isScalingDisabled()? 90 : 75\n\n                            ImageButton {\n                                objectName: \"rdm_server_action_info\"\n\n                                Layout.fillWidth: true\n                                Layout.rowSpan: 2\n                                implicitHeight: tileGrid.tileSize * 2\n\n                                tooltip: qsTranslate(\"RESP\", \"View Server Info\")\n\n                                showBorder: true\n                                imgStickTop: true\n\n                                imgWidth: tileGrid.tileIconSize\n                                imgHeight: tileGrid.tileIconSize\n                                iconSource: PlatformUtils.getThemeIcon(\"server-config.svg\")\n                                onClicked: {\n                                    currentAction.text = tooltip\n                                    serverStackView.push(serverConfig)\n                                }\n\n                                GridLayout {\n                                     anchors.bottom: parent.bottom\n                                     anchors.horizontalCenter: parent.horizontalCenter\n                                     anchors.margins: 15\n\n                                     columns: 2\n                                     flow: GridLayout.LeftToRight\n\n                                     Text {\n                                         text: qsTranslate(\"RESP\",\"Redis Version\")\n                                         font.pointSize: 12\n                                         color: sysPalette.windowText\n                                     }\n\n                                     BetterLabel {\n                                         id: redisVersionLabel\n                                         text: \"N/A\"\n                                         font.pointSize: 12\n                                         objectName: \"rdm_server_info_redis_version\"\n                                     }\n\n                                     Text {\n                                         text: qsTranslate(\"RESP\",\"Uptime\")\n                                         font.pointSize: 12\n                                         color: sysPalette.windowText\n                                     }\n\n                                     BetterLabel {\n                                         id: uptimeLabel;\n                                         text: \"N/A\";\n                                         font.pointSize: 12\n                                         objectName: \"rdm_server_info_uptime\"\n                                     }\n\n                                     Text {\n                                         text: qsTranslate(\"RESP\",\"Hit Ratio\")\n                                         font.pointSize: 12\n                                         color: sysPalette.windowText\n                                     }\n\n                                     BetterLabel {\n                                         id: hitRatioLabel;\n                                         text: \"N/A\";\n                                         font.pointSize: 12\n                                         objectName: \"rdm_server_info_hit_ratio\"\n                                     }\n\n                                     Text {\n                                         text: qsTranslate(\"RESP\",\"Used memory\")\n                                         font.pointSize: 12\n                                         color: sysPalette.windowText\n                                     }\n\n                                     BetterLabel {\n                                         id: usedMemoryLabel;\n                                         text: \"N/A\";\n                                         font.pointSize: 12\n                                         objectName: \"rdm_server_info_used_memory\"\n                                     }\n\n                                     Text {\n                                         text: qsTranslate(\"RESP\",\"Cmd Processed\")\n                                         font.pointSize: 12\n                                         color: sysPalette.windowText\n                                         wrapMode: Text.WordWrap\n                                     }\n\n                                     BetterLabel {\n                                         id: totalCommandsProcessedLabel;\n                                         text: \"N/A\";\n                                         font.pointSize: 12\n                                         objectName: \"rdm_server_info_cmd_processed\"\n                                     }\n                                }\n                            }\n\n                            ImageButton {\n                                objectName: \"rdm_server_action_monitor\"\n\n                                Layout.fillWidth: true\n                                implicitHeight: tileGrid.tileSize\n\n                                text: qsTranslate(\"RESP\", \"Monitor Commands\")\n\n                                showBorder: true\n\n                                imgWidth: tileGrid.tileIconSize\n                                imgHeight: tileGrid.tileIconSize\n                                iconSource: PlatformUtils.getThemeIcon(\"console.svg\")\n                                onClicked: {\n                                    serverTab.model.monitorCommands()\n                                }\n                            }\n\n                            ImageButton {\n                                objectName: \"rdm_server_action_slowlog\"\n\n                                Layout.fillWidth: true\n                                implicitHeight: tileGrid.tileSize\n\n                                text: qsTranslate(\"RESP\", \"Slowlog\")\n\n                                showBorder: true\n\n                                imgWidth: tileGrid.tileIconSize\n                                imgHeight: tileGrid.tileIconSize\n                                iconSource: PlatformUtils.getThemeIcon(\"slowlog.svg\")\n                                onClicked: {\n                                    currentAction.text = text\n                                    serverTab.model.refreshSlowLog = true\n                                    serverStackView.push(serverSlowlog)\n                                }\n                            }\n\n                            ImageButton {\n                                id: connectedClientsBtn\n                                objectName: \"rdm_server_action_clients\"\n\n                                Layout.fillWidth: true\n                                implicitHeight: tileGrid.tileSize\n\n                                text: qsTranslate(\"RESP\", \"Clients\")\n\n                                showBorder: true\n\n                                imgWidth: tileGrid.tileIconSize\n                                imgHeight: tileGrid.tileIconSize\n                                iconSource: PlatformUtils.getThemeIcon(\"clients.svg\")\n                                onClicked: {\n                                    currentAction.text = text\n                                    serverTab.model.refreshClients = true\n                                    serverStackView.push(serverClients)\n                                }\n                            }\n\n                            ImageButton {\n                                objectName: \"rdm_server_action_charts\"\n\n                                Layout.fillWidth: true\n                                implicitHeight: tileGrid.tileSize\n\n                                text: qsTranslate(\"RESP\", \"Server Stats\")\n\n                                showBorder: true\n\n                                imgWidth: tileGrid.tileIconSize\n                                imgHeight: tileGrid.tileIconSize\n                                iconSource: PlatformUtils.getThemeIcon(\"server-stats.svg\")\n                                onClicked: {\n                                    currentAction.text = text\n                                    serverStackView.push(serverCharts)\n                                }\n                            }\n\n                            ImageButton {\n                                objectName: \"rdm_server_action_console\"\n\n                                Layout.fillWidth: true\n                                implicitHeight: tileGrid.tileSize\n\n                                text: qsTranslate(\"RESP\", \"Console\")\n\n                                showBorder: true\n\n                                imgWidth: tileGrid.tileIconSize\n                                imgHeight: tileGrid.tileIconSize\n                                iconSource: PlatformUtils.getThemeIcon(\"console.svg\")\n                                onClicked: {\n                                    serverTab.model.openTerminal()\n                                }\n                            }\n\n                            ImageButton {\n                                objectName: \"rdm_server_action_pubsub\"\n\n                                Layout.fillWidth: true\n                                implicitHeight: tileGrid.tileSize\n\n                                text: qsTranslate(\"RESP\", \"Pub/Sub Channels\")\n\n                                showBorder: true\n\n                                imgWidth: tileGrid.tileIconSize\n                                imgHeight: tileGrid.tileIconSize\n                                iconSource: PlatformUtils.getThemeIcon(\"pub-sub-channels.svg\")\n                                onClicked: {\n                                    currentAction.text = text\n                                    serverTab.model.refreshPubSubMonitor = true\n                                    serverStackView.push(serverPubSub)\n                                }\n                            }\n\n                            Connections {\n                                target: model? model : null\n\n                                function onServerInfoChanged() {\n                                    usedMemoryLabel.text = serverTab.getValue(\"memory\", \"used_memory_human\")\n                                    redisVersionLabel.text = serverTab.getValue(\"server\", \"redis_version\")\n                                    connectedClientsBtn.text = qsTranslate(\"RESP\", \"Clients\") + \" \" + serverTab.getValue(\"clients\", \"connected_clients\")\n                                    totalCommandsProcessedLabel.text = serverTab.getValue(\"stats\", \"total_commands_processed\")\n                                    uptimeLabel.text = serverTab.getValue(\"server\", \"uptime_in_days\") + qsTranslate(\"RESP\",\" day(s)\")\n                                    hitRatioLabel.text = serverTab.getHitRatio() + \"%\"\n                                }\n                            }\n                        }                        \n\n                        Item {\n                            Layout.fillHeight: true\n                        }\n                    }\n                }\n\n                Component {\n                    id: serverCharts\n\n                    ServerCharts {\n                        model: serverTab.model\n                    }\n                }\n\n                Component {\n                    id: serverClients\n\n                    ServerClients {\n                        model: serverTab.model\n                    }\n                }\n\n                Component {\n                    id: serverConfig\n\n                    ServerConfig {\n                        model: serverTab.model\n                    }\n                }\n\n                Component {\n                    id: serverPubSub\n\n                    ServerPubSub {\n                        model: serverTab.model\n                    }\n                }\n\n                Component {\n                    id: serverSlowlog\n\n                    ServerSlowlog {\n                        model: serverTab.model\n                    }\n                }                \n\n                RowLayout {\n                    visible: serverStackView.depth > 1\n\n                    BetterButton {\n                        text: qsTr(\"Server Actions\")\n\n                        onClicked: {\n                            serverStackView.currentItem.stopTimer()\n                            serverStackView.pop()\n                        }\n                    }\n\n                    BetterLabel { text: \"❯\" }\n\n                    BetterLabel {id: currentAction}\n\n                }\n\n                StackView {\n                    id: serverStackView\n                    Layout.fillHeight: true\n                    Layout.fillWidth: true\n\n                    initialItem: actionsMenu\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/server-actions/ServerCharts.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.13\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport QtQuick.Window 2.2\nimport QtCharts 2.3\nimport \"./../common\"\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../settings\"\n\nServerAction {\n    id: infoCharts\n    clip: true\n\n    ScrollView {\n        width: parent.width\n        height: parent.height\n        ScrollBar.horizontal.policy: ScrollBar.AlwaysOff\n\n        GridLayout {\n            id: infoChartsGrid\n            columns: 2\n            columnSpacing: 0\n            rowSpacing: 20\n\n            width: infoCharts.width - 25\n            height: implicitHeight\n\n            property int chartWidth: infoCharts.width / 2\n            property int chartHeight: (infoCharts.height - rowSpacing) / 2\n\n            function chartTheme() {\n                if (sysPalette.base.hslLightness < 0.4) {\n                    return ChartView.ChartThemeDark\n                } else {\n                    return ChartView.ChartThemeLight\n                }\n            }\n\n            ChartView {\n                id: chartCommandsPerSec\n\n                Layout.preferredWidth: parent.chartWidth\n                Layout.preferredHeight: parent.chartHeight\n\n                legend.visible: false\n                backgroundRoundness: 0\n\n                theme: parent.chartTheme()\n\n                backgroundColor: sysPalette.base\n\n                title: qsTranslate(\"RESP\",\"Commands Per Second\")\n                antialiasing: true\n\n                DateTimeAxis {\n                    id: axisXCommandsPerSec\n                    min: new Date()\n                    format: \"HH:mm:ss\"\n                }\n\n                ValueAxis {\n                    id: axisYCommandsPerSec\n                    min: 0\n                    max: 100\n                    labelFormat: \"%d\"\n                    titleText: qsTranslate(\"RESP\",\"Ops/s\")\n                }\n\n                LineSeries {\n                    id: commands_per_sec_series\n                    name: \"commands_per_sec\"\n                    axisX: axisXCommandsPerSec\n                    axisY: axisYCommandsPerSec\n                }\n            }\n\n            ChartView {\n                id: chartConnectedClients\n\n                Layout.preferredWidth: parent.chartWidth\n                Layout.preferredHeight: parent.chartHeight\n\n                legend.visible: false\n                backgroundRoundness: 0\n\n                theme: parent.chartTheme()\n\n                backgroundColor: sysPalette.base\n\n                title: qsTranslate(\"RESP\",\"Connected Clients\")\n                antialiasing: true\n\n                DateTimeAxis {\n                    id: axisXConnectedClients\n                    min: new Date()\n                    format: \"HH:mm:ss\"\n                }\n\n                ValueAxis {\n                    id: axisYConnectedClients\n                    min: 0\n                    max: 100\n                    labelFormat: \"%d\"\n                    titleText: qsTranslate(\"RESP\",\"Clients\")\n                }\n\n                SplineSeries {\n                    id: connected_clients_series\n                    name: \"connected_clients\"\n                    axisX: axisXConnectedClients\n                    axisY: axisYConnectedClients\n                }\n            }\n\n            ChartView {\n                id: chartMemoryUsage\n                objectName: \"rdm_server_info_tab_memory_usage\"\n\n                Layout.preferredWidth: parent.chartWidth\n                Layout.preferredHeight: parent.chartHeight\n\n                legend.visible: false\n                backgroundRoundness: 0\n\n                theme: parent.chartTheme()\n                backgroundColor: sysPalette.base\n\n                title: qsTranslate(\"RESP\",\"Memory Usage\")\n                antialiasing: true\n\n                DateTimeAxis {\n                    id: axisXMemoryUsage\n                    min: new Date()\n                    format: \"HH:mm:ss\"\n                }\n\n                ValueAxis {\n                    id: axisYMemoryUsage\n                    min: 0\n                    titleText: qsTranslate(\"RESP\",\"Mb\")\n                }\n\n                function toMsecsSinceEpoch(date) {\n                    var msecs = date.getTime();\n                    return msecs;\n                }\n\n                SplineSeries {\n                    id: used_memory_series\n                    name: \"used_memory\"\n                    axisX: axisXMemoryUsage\n                    axisY: axisYMemoryUsage\n                }\n            }\n\n            ChartView {\n                id: chartNetworkInput\n\n                Layout.preferredWidth: parent.chartWidth\n                Layout.preferredHeight: parent.chartHeight\n\n                legend.visible: false\n                backgroundRoundness: 0\n\n                theme: parent.chartTheme()\n\n                backgroundColor: sysPalette.base\n\n                title: qsTranslate(\"RESP\",\"Network Input\")\n                antialiasing: true\n\n                DateTimeAxis {\n                    id: axisXNetworkInput\n                    min: new Date()\n                    format: \"HH:mm:ss\"\n                }\n\n                ValueAxis {\n                    id: axisYNetworkInput\n                    min: 0\n                    titleText: qsTranslate(\"RESP\",\"Kb/s\")\n                }\n\n                LineSeries {\n                    id: network_input_series\n                    name: \"network_input\"\n                    axisX: axisXNetworkInput\n                    axisY: axisYNetworkInput\n                }\n            }\n\n            ChartView {\n                id: chartNetworkOutput\n\n                Layout.preferredWidth: parent.chartWidth\n                Layout.preferredHeight: parent.chartHeight\n\n                legend.visible: false\n                backgroundRoundness: 0\n\n                theme: parent.chartTheme()\n\n                backgroundColor: sysPalette.base\n\n                title: qsTranslate(\"RESP\",\"Network Output\")\n                antialiasing: true\n\n                DateTimeAxis {\n                    id: axisXNetworkOutput\n                    min: new Date()\n                    format: \"HH:mm:ss\"\n                }\n\n                ValueAxis {\n                    id: axisYNetworkOutput\n                    min: 0\n                    titleText: qsTranslate(\"RESP\",\"Kb/s\")\n                }\n\n                LineSeries {\n                    id: network_output_series\n                    name: \"network_output\"\n                    axisX: axisXNetworkOutput\n                    axisY: axisYNetworkOutput\n                }\n            }\n\n            ChartView {\n                id: chartTotalKeys\n\n                Layout.preferredWidth: parent.chartWidth\n                Layout.preferredHeight: parent.chartHeight\n\n                legend.visible: false\n                backgroundRoundness: 0\n\n                theme: parent.chartTheme()\n\n                backgroundColor: sysPalette.base\n\n                title: qsTranslate(\"RESP\",\"Total Error Replies\")\n                antialiasing: true\n\n                DateTimeAxis {\n                    id: axisXTotalErrors\n                    min: new Date()\n                    format: \"HH:mm:ss\"\n                }\n\n                ValueAxis {\n                    id: axisYTotalErrors\n                    min: 0\n                    max: 100\n                    labelFormat: \"%d\"\n                    titleText: qsTranslate(\"RESP\",\"Error Replies\")\n                }\n\n                LineSeries {\n                    id: total_errors_series\n                    name: \"total_errors\"\n                    axisX: axisXTotalErrors\n                    axisY: axisYTotalErrors\n                }\n            }\n        }\n    }\n\n    Connections {\n        target: infoCharts.model? infoCharts.model : null\n\n        function onServerInfoChanged() {\n            if (uiBlocked) {\n                uiBlocked = false\n            }\n\n            var getUsedMemory = function (name) {\n                return Math.round(parseFloat(infoCharts.model.serverInfo[\"memory\"][name] ) / (1024 * 1024)  * 100) / 100;\n            }\n\n            // Commands per second\n            var commandsPerSec = parseInt(serverTab.getValue(\"stats\", \"instantaneous_ops_per_sec\"))\n            var commandsPerSecMax = commandsPerSec + (10 - commandsPerSec % 10)\n            if (commandsPerSecMax > axisYCommandsPerSec.max)\n                axisYCommandsPerSec.max = commandsPerSecMax\n            qmlUtils.addNewValueToDynamicChart(commands_per_sec_series, commandsPerSec)\n\n            // Connected clients\n            var connectedClients = parseInt(serverTab.getValue(\"clients\", \"connected_clients\"))\n            var connectedClientsMax = connectedClients + (10 - connectedClients % 10)\n            if (connectedClientsMax > axisYConnectedClients.max)\n                axisYConnectedClients.max = connectedClientsMax\n            qmlUtils.addNewValueToDynamicChart(connected_clients_series, connectedClients)\n\n            // Memory usage\n            var usedMemory = getUsedMemory(\"used_memory\")\n            var memoryUsageMax = getUsedMemory(\"used_memory\") + (10 - usedMemory % 10)\n            if (memoryUsageMax > axisYMemoryUsage.max)\n                axisYMemoryUsage.max = memoryUsageMax\n            qmlUtils.addNewValueToDynamicChart(used_memory_series, usedMemory)\n\n            // Network input\n            var networkInput = parseFloat(serverTab.getValue(\"stats\", \"instantaneous_input_kbps\"))\n            var networkInputMax = networkInput + (10 - networkInput % 10)\n            if (networkInputMax > axisYNetworkInput.max)\n                axisYNetworkInput.max = networkInputMax\n            qmlUtils.addNewValueToDynamicChart(network_input_series, networkInput)\n\n            // Network output\n            var networkOutput = parseFloat(serverTab.getValue(\"stats\", \"instantaneous_output_kbps\"))\n            var networkOutputMax = networkOutput + (10 - networkOutput % 10)\n            if (networkOutputMax > axisYNetworkOutput.max)\n                axisYNetworkOutput.max = networkOutputMax\n            qmlUtils.addNewValueToDynamicChart(network_output_series, networkOutput)\n\n            // Total errors\n            var totalErrors = parseInt(serverTab.getValue(\"stats\", \"total_error_replies\"))\n            var totalErrorsMax = totalErrors + (10 - totalErrors % 10)\n            if (totalErrorsMax > axisYTotalErrors.max)\n                axisYTotalErrors.max = totalErrorsMax\n            qmlUtils.addNewValueToDynamicChart(total_errors_series, totalErrors)\n        }\n    }    \n}\n"
  },
  {
    "path": "src/qml/server-actions/ServerClients.qml",
    "content": "import QtQuick 2.15\nimport QtQuick.Layouts 1.13\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport Qt.labs.qmlmodels 1.0\nimport QtQuick.Window 2.2\nimport QtCharts 2.3\nimport \"./../common\"\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../settings\"\nimport \"./../value-editor\"\n\nServerAction {\n    id: tab\n\n    Connections {\n        target: tab.model? tab.model : null\n\n        function onClientsChanged() {\n            if (uiBlocked) {\n                uiBlocked = false\n            }\n        }\n    }\n\n    ColumnLayout {\n\n        anchors.fill: parent\n        anchors.margins: 10\n\n        BoolOption {\n            Layout.preferredWidth: 200\n            Layout.preferredHeight: 40\n\n            value: true\n            label: qsTranslate(\"RESP\",\"Auto Refresh\")\n\n            onValueChanged: {\n                tab.model.refreshClients = value\n            }\n        }        \n\n        LegacyTableView {\n            Layout.fillHeight: true\n            Layout.fillWidth: true\n\n            model: tab.model.clients ? tab.model.clients : []\n\n            LC.TableViewColumn {\n                role: \"addr\"\n                title: qsTranslate(\"RESP\",\"Client Address\")\n                width: 200\n            }\n\n            LC.TableViewColumn {\n                role: \"age\"\n                title: qsTranslate(\"RESP\",\"Age (sec)\")\n                width: 75\n            }\n\n            LC.TableViewColumn {\n                role: \"idle\"\n                title: qsTranslate(\"RESP\",\"Idle\")\n                width: 75\n            }\n\n            LC.TableViewColumn {\n                role: \"flags\"\n                title: qsTranslate(\"RESP\",\"Flags\")\n                width: 75\n            }\n\n            LC.TableViewColumn {\n                role: \"db\"\n                title: qsTranslate(\"RESP\",\"Current Database\")\n                width: 120\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/server-actions/ServerConfig.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.13\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport QtQuick.Window 2.2\nimport QtCharts 2.3\nimport \"./../common\"\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../settings\"\n\nServerAction {\n    id: tab\n\n    uiBlocked: !serverInfoBuilder.model\n\n    ColumnLayout {\n\n        anchors.fill: parent\n        anchors.margins: 10\n\n        BoolOption {\n            id: autorefreshSwitch\n\n            Layout.preferredWidth: 200\n            Layout.preferredHeight: 40\n\n            value: true\n            label: qsTranslate(\"RESP\",\"Auto Refresh\")\n        }\n\n        TabBar {\n            id: serverInfoDetailsTabBar\n            Layout.fillWidth: true\n            Layout.preferredHeight: 30\n            visible: !uiBlocked\n\n            currentIndex: 0\n\n            Repeater {\n                id: serverInfoBuilderTabButtons\n                TabButton {\n                    text: modelData['name']\n                    implicitWidth: 100\n                }\n            }\n        }\n\n        StackLayout {\n            id: serverInfoTabs\n\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            Layout.topMargin: 15\n\n            currentIndex: serverInfoDetailsTabBar.currentIndex\n\n            Repeater {\n                id: serverInfoBuilder\n\n                LegacyTableView {\n\n                    model: modelData['section_data']\n\n                    LC.TableViewColumn {\n                        role: \"name\"\n                        title: qsTranslate(\"RESP\",\"Property\")\n                        width: 250\n                    }\n\n                    LC.TableViewColumn {\n                        role: \"value\"\n                        title: qsTranslate(\"RESP\",\"Value\")\n                        width: 350\n                    }\n                }\n\n            }\n\n            Connections {\n                target: tab.model? tab.model : null\n\n                function onServerInfoChanged() {\n                    if (autorefreshSwitch.value === false)\n                        return;\n\n                    loadServerInfo();\n\n                    if (uiBlocked)\n                        uiBlocked = false;\n                }\n\n                function loadServerInfo() {\n                    var sections = []\n\n                    for (var section in tab.model.serverInfo) {\n                        var section_data = []\n                        for (var key in tab.model.serverInfo[section])\n                        {\n                            var property = {\"name\": key, \"value\": tab.model.serverInfo[section][key]}\n                            section_data.push(property)\n                        }\n                        sections.push({\"name\": section, \"section_data\": section_data})\n                    }\n\n                    var currentTab = serverInfoTabs.currentIndex\n                    serverInfoBuilder.model = sections\n                    serverInfoBuilderTabButtons.model = sections\n                    serverInfoDetailsTabBar.currentIndex = currentTab\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/server-actions/ServerPubSub.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.13\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport QtQuick.Window 2.2\nimport QtCharts 2.3\nimport \"./../common\"\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../settings\"\n\nServerAction {\n    id: tab\n\n    function stopTimer() {\n        model.refreshPubSubMonitor = false\n    }\n\n    Connections {\n        target: tab.model? tab.model : null\n\n        function onPubSubChannelsChanged() {\n            if (uiBlocked) {\n                uiBlocked = false\n            }\n        }\n    }\n\n    ColumnLayout {\n\n        anchors.fill: parent\n        anchors.margins: 10\n\n        BoolOption {\n            Layout.preferredWidth: 200\n            Layout.preferredHeight: 40\n\n            value: true\n            label: qsTranslate(\"RESP\",\"Enable\")\n\n            onValueChanged: {\n                tab.model.refreshPubSubMonitor = value\n            }\n        }\n\n        LegacyTableView {\n            Layout.fillHeight: true\n            Layout.fillWidth: true\n\n            model: tab.model.pubSubChannels ? tab.model.pubSubChannels : []\n\n            rowDelegate: Item {\n                height: 50\n            }\n\n            LC.TableViewColumn {\n                role: \"addr\"\n                title: qsTranslate(\"RESP\",\"Channel Name\")\n                width: 200\n            }\n\n            LC.TableViewColumn {\n                role: \"addr\"\n                width: 200\n                delegate: Item {\n                    BetterButton {\n                        objectName: \"rdm_server_info_pub_sub_subscribe_to_channel_btn\"\n                        anchors.centerIn: parent\n                        text: qsTranslate(\"RESP\",\"Subscribe in Console\")\n                        onClicked: {\n                            console.log(styleData.value)\n                            tab.model.subscribeToChannel(styleData.value)\n                        }\n                    }\n                }\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/server-actions/ServerSlowlog.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.13\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport QtQuick.Window 2.2\nimport QtCharts 2.3\nimport \"./../common\"\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../settings\"\n\nServerAction {\n    id: tab\n\n    function stopTimer() {\n        model.refreshSlowLog = false\n    }\n\n\n    Connections {\n        target: tab.model? tab.model : null\n\n        function onSlowLogChanged() {\n            if (uiBlocked) {\n                uiBlocked = false\n            }\n        }\n    }\n\n    ColumnLayout {\n\n        anchors.fill: parent\n        anchors.margins: 10\n\n        BoolOption {\n            Layout.preferredWidth: 200\n            Layout.preferredHeight: 40\n\n            value: true\n            label: qsTranslate(\"RESP\",\"Auto Refresh\")\n\n            onValueChanged: {\n                tab.model.refreshSlowLog = value\n            }\n        }\n\n        LegacyTableView {\n            Layout.fillHeight: true\n            Layout.fillWidth: true\n\n            model: tab.model.slowLog ? tab.model.slowLog : []\n\n            LC.TableViewColumn {\n                role: \"cmd\"\n                title: qsTranslate(\"RESP\",\"Command\")\n                width: 600\n\n                delegate: BetterLabel {\n                    text: {\n                        var result = \"\";\n                        for (var index in modelData['cmd']) {\n                            result += modelData['cmd'][index] + \" \";\n                        }\n                        return result;\n                    }\n                    elide: styleData.elideMode\n                }\n            }\n\n            LC.TableViewColumn {\n                role: \"time\"\n                title: qsTranslate(\"RESP\",\"Processed at\")\n                width: 150\n\n                delegate: BetterLabel {\n                    text: {\n                        return new Date(modelData['time']*1000).toLocaleString(\n                                    locale, PlatformUtils.dateTimeFormat);\n                    }\n                    elide: styleData.elideMode\n                }\n\n            }\n\n            LC.TableViewColumn {\n                role: \"exec_time\"\n                title: qsTranslate(\"RESP\",\"Execution Time (μs)\")\n                width: 150\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/settings/BoolOption.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.3\nimport \"../common\"\n\nItem {\n    id: root\n    property string label\n    property string description\n    property bool value\n\n    onValueChanged: {\n        if (val.checked != root.value) {\n            val.checked = root.value\n        }\n    }\n\n    signal clicked\n\n    RowLayout {\n        anchors.fill: parent\n\n        ColumnLayout {\n            Layout.fillWidth: true\n            spacing: 1\n\n            BetterLabel {\n                Layout.fillWidth: true\n                text: root.label\n            }\n\n            Text {\n                color: disabledSysPalette.text\n                text: root.description\n                visible: root.description\n            }\n        }\n\n        Switch {\n            id: val\n            rightPadding: 0\n\n            indicator: Rectangle {\n                    implicitWidth: 48\n                    implicitHeight: 26\n                    x: val.leftPadding\n                    y: parent.height / 2 - height / 2\n                    radius: 13\n                    color: val.checked ? sysPalette.highlight : sysPalette.button\n                    border.color: val.checked ? sysPalette.highlight : sysPalette.button\n\n                    Rectangle {\n                        x: val.checked ? parent.width - width : 0\n                        width: 26\n                        height: 26\n                        radius: 13\n                        color: val.checked ? sysPalette.midlight : sysPalette.mid\n                        border.color: val.checked ? sysPalette.highlight : sysPalette.button\n                    }\n                }\n\n            onCheckedChanged: {\n                root.value = checked                \n            }\n\n            onClicked: {\n                root.clicked()\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/qml/settings/ComboboxOption.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.2\n\nimport \"../common\"\n\nItem {\n    id: root\n    property string label\n    property string description\n    property string value\n    property alias model: val.model\n    property int popupMaxHeight: 350\n    property int popupWidth: val.width\n\n    onValueChanged: {        \n        if (val.currentText != root.value) {\n            val.currentIndex = val.find(root.value)\n        }\n    }\n\n    RowLayout {\n        anchors.fill: parent\n\n        ColumnLayout {\n            Layout.fillWidth: true\n            spacing: 1\n\n            BetterLabel {\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                text: root.label\n            }\n\n            Text {\n                color: \"grey\"\n                text: root.description\n                visible: !!text\n            }\n        }\n\n        BetterComboBox {\n            id: val\n\n            Layout.minimumWidth: 80\n\n            onActivated: {\n                if (index >= 0)\n                    root.value = textAt(index)\n            }\n\n            onCountChanged: {\n                if (model) {\n                    currentIndex = val.find(root.value)                    \n                }\n            }\n\n            Component.onCompleted: {                \n                if (model) {\n                    currentIndex = val.find(root.value)\n                }\n\n                popup.contentItem.implicitHeight = Qt.binding(function () {\n                    return Math.min(root.popupMaxHeight,\n                                    val.popup.contentItem.contentHeight);\n                });\n\n                popup.width = Qt.binding(function () {\n                    return Math.max(root.popupWidth,\n                                    val.popup.contentItem.contentWidth);\n                });\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/qml/settings/FontSizeOption.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.2\n\nimport \"../common\"\nimport \".\"\n\nComboboxOption {\n    id: root\n\n    property int minFontSize: 4\n    property int maxFontSize: 16\n\n    Component.onCompleted: {\n        var m = []\n        for (var c=minFontSize; c <= maxFontSize; c++) {\n            m.push(c)\n        }\n        model = m;\n    }\n}\n\n"
  },
  {
    "path": "src/qml/settings/GlobalSettings.qml",
    "content": "import QtQuick 2.15\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls 1.4 as LC\nimport Qt.labs.settings 1.0\nimport QtQuick.Window 2.3\nimport \"../common\"\nimport \".\"\nimport \"../common/platformutils.js\" as PlatformUtils\n\nBetterDialog {\n    id: root\n    title: qsTranslate(\"RESP\",\"Settings\")\n\n    footer: null\n\n    property bool restartRequired: false\n\n    contentItem: Rectangle {\n        id: dialogRoot\n        implicitWidth:  PlatformUtils.isScalingDisabled() ? 1100 : 950\n        implicitHeight: PlatformUtils.isScalingDisabled() ? 700 : 550\n\n        color: sysPalette.base\n\n        Control {\n            palette: approot.palette\n            anchors.fill: parent\n            anchors.margins: 20\n\n            ScrollView {\n                id: globalSettingsScrollView\n                width: parent.width\n                height: parent.height\n                ScrollBar.horizontal.policy: ScrollBar.AlwaysOff\n\n                ColumnLayout {\n                    id: innerLayout\n                    width: globalSettingsScrollView.width - 25\n                    height: (dialogRoot.height - 50 > implicitHeight) ? dialogRoot.height - 50 : implicitHeight\n                    spacing: PlatformUtils.isScalingDisabled()? 20 : 10\n\n                    RowLayout {\n                        Layout.fillWidth: true\n\n                        SettingsGroupTitle {\n                            Layout.fillWidth: true\n                            text: qsTranslate(\"RESP\",\"General\")\n                        }\n\n                        BetterLabel {\n                            color: disabledSysPalette.text\n                            text: qsTranslate(\"RESP\",\"Application will be restarted to apply these settings.\")\n                        }\n                    }\n\n                    GridLayout {\n                        columns: 2\n                        rows: 3\n                        flow: GridLayout.TopToBottom\n                        Layout.fillWidth: true\n                        rowSpacing: PlatformUtils.isScalingDisabled() ? 20 : 10\n                        columnSpacing: PlatformUtils.isScalingDisabled() ? 20 : 15\n\n                        ComboboxOption {\n                            id: appLang\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            model: [\"system\", \"en_US\", \"zh_CN\", \"zh_TW\", \"uk_UA\", \"es_ES\", \"ja_JP\"]\n                            value: \"system\"\n                            label: qsTranslate(\"RESP\",\"Language\")\n                            onValueChanged: root.restartRequired = true\n                        }\n\n                        ComboboxOption {\n                            id: appFont\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n                            popupWidth: 300\n\n                            model: Qt.fontFamilies()\n                            label: qsTranslate(\"RESP\",\"Font\")\n\n                            onValueChanged: root.restartRequired = true\n\n                        }\n\n                        FontSizeOption {\n                            id: appFontSize\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            label: qsTranslate(\"RESP\",\"Font Size\")\n\n                            onValueChanged: root.restartRequired = true                            \n                        }\n\n                        ComboboxOption {\n                            id: darkModeWindows\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            model: [\"Auto\", \"On\", \"Off\"]\n                            value: \"Auto\"\n                            label: qsTranslate(\"RESP\",\"Dark Mode\")\n\n                            visible: PlatformUtils.isWindows()\n\n                            onValueChanged: root.restartRequired = true\n                        }\n\n                        BoolOption {\n                            id: darkModeLinux\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            value: false\n                            label: qsTranslate(\"RESP\",\"Dark Mode\")\n\n                            visible: PlatformUtils.isLinux()\n\n                            onValueChanged: root.restartRequired = true\n                        }\n\n                        BoolOption {\n                            id: systemProxy\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            value: false\n                            label: qsTranslate(\"RESP\",\"Use system proxy settings\")\n\n                            onValueChanged: root.restartRequired = true\n                        }\n\n                        BoolOption {\n                            id: disableProxyForRedisConnections\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            value: false\n                            label: qsTranslate(\"RESP\",\"Use system proxy only for HTTP(S) requests\")\n                        }\n                    }\n\n                    SettingsGroupTitle {\n                        Layout.topMargin: 10\n                        text: qsTranslate(\"RESP\",\"Value Editor\")\n                    }\n\n                    GridLayout {\n                        columns: 2\n                        rows: 2\n                        flow: GridLayout.TopToBottom\n                        rowSpacing: PlatformUtils.isScalingDisabled() ? 20 : 10\n                        columnSpacing: PlatformUtils.isScalingDisabled() ? 20 : 15\n\n                        ComboboxOption {\n                            id: valueEditorFont\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n                            popupWidth: 300\n\n                            model: Qt.fontFamilies()\n                            label: qsTranslate(\"RESP\",\"Font\")\n\n                            onValueChanged: root.restartRequired = true\n                        }\n\n                        FontSizeOption {\n                            id: valueEditorFontSize\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            value: Qt.platform.os == \"osx\"? \"12\" : \"11\"\n                            label: qsTranslate(\"RESP\",\"Font Size\")\n\n                            onValueChanged: root.restartRequired = true\n                        }\n\n                        IntOption {\n                            id: valueSizeLimit\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            min: 1000\n                            max: 20000000\n                            value: 1500000\n                            label: qsTranslate(\"RESP\",\"Maximum Formatted Value Size\")\n                            description: qsTranslate(\"RESP\", \"Size in bytes\")\n                        }\n\n                        IntOption {\n                            id: valueEditorPageSizeControl\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            min: 10\n                            max: 10000\n                            value: 100\n                            label: qsTranslate(\"RESP\",\"Maximum amount of items per page\")\n                        }\n                    }\n\n                    SettingsGroupTitle {\n                        text: qsTranslate(\"RESP\",\"Connections Tree\")\n                        Layout.topMargin: 20\n                    }\n\n                    GridLayout {\n                        columns: 2\n                        rows: 4\n                        flow: GridLayout.TopToBottom\n                        rowSpacing: PlatformUtils.isScalingDisabled() ? 20 : 10\n                        columnSpacing: PlatformUtils.isScalingDisabled() ? 20 : 15\n\n                        BoolOption {\n                            id: nsOnTop\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            value: Qt.platform.os == \"windows\"? true : false\n                            label: qsTranslate(\"RESP\",\"Show namespaced keys on top\")\n                        }\n\n                        BoolOption {\n                            id: nsReload\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            value: true\n                            label: qsTranslate(\"RESP\",\"Reopen namespaces on reload\")\n                            description: qsTranslate(\"RESP\",\"(Disable to improve treeview performance)\")\n                        }\n\n                        BoolOption {\n                            id: namespacedKeysShortName\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n                            Layout.rowSpan: 2\n\n                            value: true\n                            label: qsTranslate(\"RESP\",\"Show only last part for namespaced keys\")\n                        }\n\n                        IntOption {\n                            id: scanCommandLimit\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            min: 1000\n                            max: 500000\n                            value: 10000\n                            label: qsTranslate(\"RESP\",\"Limit for SCAN command\")\n                        }\n\n                        IntOption {\n                            id: childItemsLimit\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            min: 1\n                            max: 100000\n                            value: 1000\n                            label: qsTranslate(\"RESP\",\"Maximum amount of rendered child items\")\n                        }\n\n                        IntOption {\n                            id: liveKeyLimit\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            min: 100\n                            max: 100000\n                            value: 1000\n                            label: qsTranslate(\"RESP\",\"Live update maximum allowed keys\")\n                        }\n\n                        IntOption {\n                            id: liveUpdateInterval\n\n                            Layout.fillWidth: true\n                            Layout.preferredHeight: 30\n\n                            min: 3\n                            max: 100000\n                            value: 10\n                            label: qsTranslate(\"RESP\",\"Live update interval (in seconds)\")\n                        }\n                    }\n\n                    Item {\n                        Layout.fillHeight: true\n                    }\n\n                    RowLayout {\n                        Layout.fillWidth: true\n\n                        Item { Layout.fillWidth: true; }\n                        BetterButton {\n                            text: qsTranslate(\"RESP\",\"OK\")\n                            onClicked: {\n                                if (!PlatformUtils.isOSX() && root.restartRequired === true) {\n                                    // restart app\n                                    Qt.exit(1001)\n                                }\n\n                                restartRequired = false\n                                root.close()\n                            }\n                        }\n                        BetterButton {\n                            text: qsTranslate(\"RESP\",\"Cancel\")\n                            onClicked: root.close()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Settings {\n        id: globalSettings\n        category: \"app\"\n\n        property alias showNamespacesOnTop: nsOnTop.value\n        property alias reopenNamespacesOnReload: nsReload.value        \n        property alias namespacedKeysShortName: namespacedKeysShortName.value\n        property alias treeItemMaxChilds: childItemsLimit.value\n        property alias liveUpdateKeysLimit: liveKeyLimit.value\n        property alias liveUpdateInterval: liveUpdateInterval.value\n        property alias appFont: appFont.value\n        property alias appFontSize: appFontSize.value\n        property alias valueEditorFont: valueEditorFont.value\n        property alias valueEditorFontSize: valueEditorFontSize.value\n        property alias valueSizeLimit: valueSizeLimit.value\n        property alias valueEditorPageSize: valueEditorPageSizeControl.value\n        property alias locale: appLang.value\n        property alias darkModeOn: darkModeLinux.value\n        property alias darkMode: darkModeWindows.value\n        property alias useSystemProxy: systemProxy.value\n        property alias disableProxyForRedisConnections: disableProxyForRedisConnections.value\n        property alias scanLimit: scanCommandLimit.value\n    }\n\n    Settings {\n        id: customFormatters\n        category: \"formatters\"\n\n        property var formatters\n    }\n\n    Component.onCompleted: {\n        restartRequired = false\n    }\n}\n"
  },
  {
    "path": "src/qml/settings/IntOption.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.3\nimport QtQuick.Controls 2.3\nimport \"./../common\"\n\nItem {\n    id: root\n    property string label\n    property string description\n    property int value\n    property alias min: val.from\n    property alias max: val.to\n\n    onValueChanged: {\n        if (val.value != root.value) {\n            val.value = root.value\n        }\n    }\n\n    RowLayout {\n        anchors.fill: parent\n\n        ColumnLayout {\n            Layout.fillWidth: true\n            spacing: 1\n\n            BetterLabel {\n                Layout.fillWidth: true\n                Layout.fillHeight: !root.description\n                text: root.label\n            }\n\n            Text {\n                color: disabledSysPalette.text\n                text: root.description\n                visible: root.description\n            }\n        }\n\n        BetterSpinBox {\n            id: val\n\n            Layout.minimumWidth: 80\n\n            onValueChanged: {\n                root.value = value\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/qml/value-editor/AddKeyDialog.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.12\nimport \"./../common\"\nimport \"./editors/editor.js\" as Editor\nimport \"../common/platformutils.js\" as PlatformUtils\n\nBetterDialog {\n    id: root\n    title: qsTranslate(\"RESP\",\"Add New Key to \") + (request? request.dbIdString: \"\")\n    visible: false\n    property var request\n    property bool loadingKeyTypes: false\n    property var supportedKeyTypes\n\n    footer: null\n\n    onRequestChanged: {\n        if (!request)\n            return;\n\n        root.loadingKeyTypes = true;\n        request.loadAdditionalKeyTypesInfo(processAdditionalKeyTypes);\n\n        root.supportedKeyTypes = Editor.getSupportedKeyTypes();\n    }\n\n\n    function processAdditionalKeyTypes() {\n        console.log(root.supportedKeyTypes)\n\n        if (arguments && arguments.length > 0) {\n            for (var indx in arguments) {\n                console.log(\"module:\", arguments[indx])\n                root.supportedKeyTypes.push(arguments[indx])\n            }\n        }\n\n        console.log(root.supportedKeyTypes)\n\n        root.supportedKeyTypes = supportedKeyTypes;\n        root.loadingKeyTypes = false;\n    }\n\n\n    Item {\n        anchors.fill: parent\n        implicitHeight: PlatformUtils.isOSX() ? 400 : 600\n        implicitWidth: PlatformUtils.isOSX() ? 600 : 800\n\n        ColumnLayout {\n            anchors.fill: parent\n            anchors.margins: 5\n\n            BetterLabel {\n                text: qsTranslate(\"RESP\",\"Key:\")\n            }\n            BetterTextField {\n                id: newKeyName\n                Layout.fillWidth: true\n                objectName: \"rdm_add_key_name_field\"\n                text: request? request.keyName : ''\n            }\n\n            BetterLabel {\n                text: qsTranslate(\"RESP\",\"Type:\")\n            }\n\n            BetterComboBox {\n                id: typeSelector\n                model: root.supportedKeyTypes\n                Layout.fillWidth: true\n                objectName: \"rdm_add_key_type_field\"\n\n                onCurrentIndexChanged: {\n                    if (valueAddEditor.item.keyType !== undefined) {\n                        valueAddEditor.item.keyType = typeSelector.model[typeSelector.currentIndex]                        \n                    }\n                }\n\n                BusyIndicator {\n                    anchors.centerIn: parent\n                    running: root.loadingKeyTypes === true\n                    width: typeSelector.height\n                }\n\n            }\n\n            Loader {\n                id: valueAddEditor\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                Layout.preferredHeight: 300\n\n                asynchronous: true\n                source: Editor.getEditorByTypeString(\n                            typeSelector.model[typeSelector.currentIndex], true)\n\n                onLoaded: {\n                    item.state = \"new\"\n                    if (item.keyType !== undefined)\n                        item.keyType = typeSelector.model[typeSelector.currentIndex]\n                    item.initEmpty()\n                }\n            }\n\n            BetterLabel { text: qsTranslate(\"RESP\", \"Or Import Value from the file\") + \":\" }\n\n            FilePathInput {\n                id: valueFilePath\n                objectName: \"rdm_add_key_value_file\"\n                Layout.fillWidth: true\n                placeholderText: qsTranslate(\"RESP\",\"(Optional) Any file\")\n                nameFilters: [ \"Any file (*)\" ]\n                title: qsTranslate(\"RESP\",\"Select file with value\")\n                path: \"\"\n            }\n\n            RowLayout {\n                Layout.fillWidth: true\n                Layout.minimumHeight: 40\n                Item {\n                    Layout.fillWidth: true\n                }\n                BetterButton {\n                    objectName: \"rdm_add_key_save_btn\"\n                    text: qsTranslate(\"RESP\",\"Save\")\n\n                    function submitNewKeyRequest(row) {\n                        root.request.keyName = newKeyName.text\n                        root.request.keyType = typeSelector.model[typeSelector.currentIndex]\n                        root.request.value = row\n                        root.request.valueFilePath = valueFilePath.path\n                        keyFactory.submitNewKeyRequest(root.request)\n                    }\n\n\n                    onClicked: {\n                        if (!valueAddEditor.item)\n                            return\n\n                        var validateVal = (valueFilePath.path === \"\")\n\n                        valueAddEditor.item.getValue(validateVal, function (valid, row) {\n                            if (!valid)\n                                return;\n\n                            submitNewKeyRequest(row);\n                        })\n                    }\n\n                    Connections {\n                        target: keyFactory\n\n                        function onKeyAdded() {\n                            root.request = null\n                            valueAddEditor.item.reset()\n                            valueAddEditor.item.initEmpty()\n                            valueFilePath.path = \"\"\n                            root.close()\n                        }\n\n                        function onError(err) {\n                            addError.text = err\n                            addError.open()\n                        }\n                    }\n\n                }\n\n                BetterButton {\n                    text: qsTranslate(\"RESP\",\"Cancel\")\n                    onClicked: root.close()\n                }\n            }\n            Item {\n                Layout.fillWidth: true\n            }\n        }\n\n        OkDialogOverlay {\n            id: addError\n            title: qsTranslate(\"RESP\",\"Error\")\n            text: \"\"\n            visible: false\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/Pagination.qml",
    "content": "import QtQuick 2.3\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport \"../common\"\n\nColumnLayout {    \n    GridLayout {\n        columns: 2\n        Layout.fillWidth: true\n\n        BetterLabel {\n            text: qsTranslate(\"RESP\",\"Page\") + \":\"\n            wrapMode: Text.WrapAnywhere\n        }\n\n        BetterTextField {\n            id: pageField;\n\n            text: table.currentPage;\n\n            tooltip: qsTranslate(\"RESP\", \"Total pages: \") + table.totalPages\n\n            Layout.fillWidth: true\n\n            validator: IntValidator {\n              locale: pageField.locale.name\n              bottom: 1\n              top: table.totalPages\n            }\n\n            onFocusChanged: {\n                if (focus)\n                    return;\n\n                text = Qt.binding(function() { return table.currentPage; });\n            }\n\n            onAccepted: {\n                table.goToPage(text)\n            }\n        }\n\n        BetterLabel {\n            Layout.columnSpan: 2\n            text:  qsTranslate(\"RESP\",\"Size: \") + keyRowsCount\n        }\n    }\n\n    RowLayout {        \n        Layout.maximumWidth: 200\n        Layout.fillWidth: true\n        spacing: 1\n        BetterButton {\n            Layout.fillWidth: true\n            palette.buttonText: sysPalette.dark\n            text: \"❮\"\n            onClicked: table.goToPrevPage()\n        }\n        BetterButton {\n            Layout.fillWidth: true\n            palette.buttonText: sysPalette.dark\n            text: \"❯\"\n            onClicked: table.goToNextPage()\n            objectName: \"rdm_value_editor_next_page_button\"\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/ValueTable.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls.Styles 1.1\nimport QtQuick.Window 2.2\nimport \"./editors/editor.js\" as Editor\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../common\"\nimport \"./filters\"\nimport rdm.models 1.0\nimport Qt.labs.qmlmodels 1.0\n\nItem {\n    id: root\n\n    property var resizeGuide: null\n\n    RowLayout {\n        anchors.fill: parent\n\n        ColumnLayout {\n            id: tableLayout\n            Layout.fillHeight: true\n            Layout.minimumHeight: 100\n\n            RowLayout {\n                Layout.minimumHeight: 15\n                Layout.preferredHeight: 30\n                Layout.fillWidth: true\n                spacing: 1\n\n                Repeater {\n                    id: tableHeader\n                    model: keyTab.keyModel? keyTab.keyModel.columnNames : []\n\n                    Rectangle {  // Table header cell\n                        objectName: \"rdm_value_tab_table_header_col\" + index\n                        Layout.preferredHeight: 30\n                        Layout.minimumWidth: {\n                            if (table.valueColumnWidthOverrides && table.valueColumnWidthOverrides[index] !== undefined) {\n                                return table.valueColumnWidthOverrides[index];\n                            }\n\n                            if (index === 0)\n                                return table.firstColumnWidth\n                            else\n                                return table.valueColumnWidth\n                        }\n                        color: sysPalette.window\n\n                        BetterLabel {\n                            anchors.centerIn: parent\n                            text: {\n                                if (modelData === \"rowNumber\") {\n                                    return \"#\";\n                                } else {\n                                    return modelData\n                                }\n                            }\n                            color: sysPalette.windowText\n                        }\n\n                        BetterLabel {  // Sort indicator\n                            anchors.margins: 10\n                            anchors.right: parent.right\n                            anchors.verticalCenter: parent.verticalCenter\n                            text: \"▲\"\n                            color: sysPalette.mid\n                            visible: false\n                        }\n\n                        MouseArea {\n                            anchors.fill: parent\n\n                            onClicked: {\n                                var role = tableHeader.model[index]\n                                var order = (role == table.model.sortRole) ? 1 - table.model.sortOrder : Qt.AscendingOrder\n\n                                for (var i = 0; i < tableHeader.model.length; i++) {\n                                    tableHeader.itemAt(i).children[1].visible = false\n                                }\n                                tableHeader.itemAt(index).children[1].text = (order === Qt.AscendingOrder) ? \"▲\" : \"▼\"\n                                tableHeader.itemAt(index).children[1].visible = true\n\n                                table.sort(role, order)\n                            }\n                        }\n\n\n                        Rectangle {\n                            id: resizeHandler\n\n                            visible: index > 0 && index < keyTab.keyModel.columnNames.length - 1\n\n                            color: \"transparent\"\n\n                            height: parent.height\n                            implicitWidth: 5\n\n                            anchors {\n                                top: parent.top\n                                topMargin: 2\n                                bottom: parent.bottom\n                                bottomMargin: 2\n                                right: parent.right\n                            }\n\n                            MouseArea {\n                                id: resizeMouseArea\n\n                                anchors.fill: parent\n\n                                cursorShape: Qt.SizeHorCursor\n                                hoverEnabled: true\n                                drag {\n                                    target: resizeHandler\n                                    minimumX: 20\n                                    smoothed: false\n                                }\n\n                                onPressed: {\n                                    resizeHandler.anchors.right = undefined;\n                                    if (root.resizeGuide !== null) {\n                                        root.resizeGuide.destroy();\n                                    }\n                                    var guide = resizeMarker.createObject(resizeHandler);\n                                    root.resizeGuide = guide;\n                                    guide.open();\n                                }\n\n                                onReleased: {\n                                    if (resizeHandler.x > 0) {\n                                        table.setColumnWidth(index, resizeHandler.x);\n                                    }\n                                    resizeHandler.anchors.right = resizeHandler.parent.right;\n                                    if (root.resizeGuide !== null) {\n                                        root.resizeGuide.destroy();\n                                    }\n                                }\n                            }\n\n                        }\n\n                        Component {\n                            id: resizeMarker\n\n                            Popup {\n                                y: resizeHandler.y + parent.height - 6\n\n                                height: table.height + 6\n                                width: 2\n\n                                background: Rectangle {\n                                    color: sysPalette.window\n                                }\n\n                                contentItem: Item {}\n                            }\n                        }\n\n                    }  // Table header cell end\n                }\n            }\n\n            Item {\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                Layout.minimumHeight: 100\n\n                ScrollView {\n                    id: tableScrollView\n                    anchors.fill: parent\n\n                    ScrollBar.horizontal.policy: ScrollBar.AlwaysOff\n                    ScrollBar.vertical.policy: ScrollBar.AlwaysOn\n\n\n                    TableView {\n                        id: table\n                        objectName: \"rdm_value_tab_table\"\n\n                        focus: true\n                        clip: true\n                        width: parent.width\n                        onWidthChanged: forceLayout()\n\n                        columnSpacing: 1\n                        rowSpacing: 1\n                        reuseItems: false\n                        model: searchModel ? searchModel : null\n\n                        // Proxy model row index from 0 to maxItemsOnPage\n                        property int currentRow: -1\n                        property var searchField\n                        property var currentStart: 0\n                        property int maxItemsOnPage: keyTab.keyModel ? keyTab.keyModel.pageSize : 100\n                        property int currentPage: currentStart / maxItemsOnPage + 1\n                        property int totalPages: keyTab.keyModel ? Math.ceil(keyTab.keyModel.totalRowCount / maxItemsOnPage) : 0\n                        property bool forceLoading: false\n                        property int firstColumnWidth: 75\n                        property int valueColumnWidth:  keyTab.keyModel && keyTab.keyModel.columnNames.length == 2? root.width - 200 - table.firstColumnWidth - table.columnSpacing\n                                                                                                                  : (root.width - 200 - table.firstColumnWidth - table.columnSpacing) / 2\n                        property var valueColumnWidthOverrides: QtObject {}\n\n                        Keys.onUpPressed: {\n                            if (currentRow > 0) {\n                                currentRow--;\n                            } else {\n                                currentRow = 0;\n                            }\n                        }\n\n                        Keys.onDownPressed: {\n                            if (currentRow < rows - 1) {\n                                currentRow++;\n                            } else {\n                                currentRow = rows - 1;\n                            }\n                        }\n\n                        columnWidthProvider: function (column) {\n                            if (column === 0) {\n                                return firstColumnWidth\n                            }\n\n                            if (valueColumnWidthOverrides && valueColumnWidthOverrides[column] !== undefined) {\n                                return valueColumnWidthOverrides[column]\n                            }\n\n                            return valueColumnWidth\n                        }\n\n                        property int minColWidth: 50\n\n                        function setColumnWidth(index, width) {\n                            if (width < minColWidth)\n                                width = minColWidth\n\n                            if (keyTab.keyModel.columnNames.length == 2) {\n                                table.valueColumnWidthOverrides[index] = width;\n                            } else {\n                                for (var i=1; i < 3; i++)\n                                {\n                                    if (i === index) {\n                                        table.valueColumnWidthOverrides[i] = width;\n                                    } else {\n                                        table.valueColumnWidthOverrides[i] = root.width - 200 - table.firstColumnWidth - table.columnSpacing - width;\n\n                                        if (table.valueColumnWidthOverrides[i] < minColWidth)\n                                            return setColumnWidth(i, minColWidth)\n                                    }\n                                }\n                            }\n\n                            table.forceLayout()\n                            tableHeader.model = []\n                            tableHeader.model = keyTab.keyModel.columnNames\n                        }\n\n                        Connections {\n                            target: root\n\n                            function onWidthChanged() {\n                                table.valueColumnWidthOverrides = {};\n                            }\n                        }\n\n\n                        Component.onCompleted: keyTab.table = table\n\n                        delegate: DelegateChooser {\n\n                            DelegateChoice {\n                                column: 0\n\n\n                                // NOTE: rowNumber - key model zero based index from 0 to rowsCount\n                                // NOTE: row - from 0 to pageSize\n\n                                ValueTableCell {\n                                    objectName: \"rdm_value_table_cell_col1\"\n                                    implicitWidth: table.firstColumnWidth\n                                    implicitHeight: 30\n                                    text: Number(rowNumber) + 1\n                                    selected: table.currentRow === row\n                                    onClicked: {\n                                        table.currentRow = row\n                                        table.forceActiveFocus()\n                                    }\n                                }\n                            }\n\n                            DelegateChoice {\n                                column: 1\n\n                                ValueTableCell {\n                                    objectName: \"rdm_value_table_cell_col2\"\n                                    implicitWidth: table.valueColumnWidth\n                                    implicitHeight: 30\n                                    text: renderText(display)\n                                    selected: table.currentRow === row\n                                    onClicked: {\n                                         table.currentRow = row\n                                        table.forceActiveFocus()\n                                    }\n                                }\n                            }\n\n                            DelegateChoice {\n                                column: 2\n\n                                ValueTableCell {\n                                    objectName: \"rdm_value_table_cell_col3\"\n                                    implicitWidth: table.valueColumnWidth\n                                    implicitHeight: 30\n\n                                    selected: table.currentRow === row\n                                    onClicked: {\n                                        table.currentRow = row\n                                        table.forceActiveFocus()\n                                    }\n\n                                    text: {\n                                        if (display === \"\" || !isMultiRow) {\n                                            return \"\"\n                                        }\n\n                                        if (keyType == \"zset\") {\n                                            return Number(display)\n                                        }\n\n                                        return renderText(display)\n                                    }\n                                }\n                            }\n                        }\n\n                        OkDialogOverlay {\n                            id: valueErrorNotification\n                            visible: false\n                        }\n\n                        Connections {\n                            id: keyModelConnections\n                            ignoreUnknownSignals: true\n\n                            target: keyTab.keyModel ? keyTab.keyModel : null\n\n                            function onError(error) {\n                                valueErrorNotification.text = error\n                                valueErrorNotification.open()\n                            }\n\n                            function onIsLoadedChanged() {\n                                console.log(\"model loaded (qml)\")\n                                if (keyTab.keyModel.totalRowCount === 0) {\n                                    console.log(\"Load rows count\")\n                                    keyTab.keyModel.loadRowsCount()\n                                } else {\n                                    console.log(\"Load rows\")\n                                    keyTab.keyModel.loadRows(currentStart, maxItemsOnPage)\n                                }\n                            }\n\n                            function onTotalRowCountChanged() {\n                                keyTab.keyModel.loadRows(table.currentStart, table.maxItemsOnPage)\n                            }\n\n                            function onRowsLoaded() {\n                                console.log(\"rows loaded\")\n\n                                wrapper.hideLoader()\n                                keyTab.searchModel = keyTab.searchModelComponent.createObject(keyTab)\n\n                                if (isMultiRow) {\n                                    valueEditor.clear()\n                                } else {\n                                    valueEditor.loadRowValue(0)\n                                }\n\n                                table.forceLayout()\n                            }\n                        }\n\n                        function goToPage(page) {\n                            var firstItemOnPage = table.maxItemsOnPage * (page - 1)\n\n                            if (table.currentStart === firstItemOnPage)\n                                return\n\n                            table.currentStart = firstItemOnPage\n                            resetCurrentRow()\n                            loadValue()\n                        }\n\n                        function goToPrevPage() {\n                            console.log('goto prev page')\n                            if (table.currentPage - 1 < 1)\n                                return\n\n                            goToPage(table.currentPage - 1)\n                        }\n\n                        function goToNextPage() {\n                            console.log('goto next page')\n                            if (table.totalPages < table.currentPage + 1)\n                                return\n\n                            goToPage(table.currentPage + 1)\n                        }\n\n                        function loadValue() {\n                            console.log(\"Load value\")\n                            if (!keyTab.keyModel) {\n                                console.log(\"Model is not ready\", keyViewModel)\n                                return\n                            }\n                            wrapper.showLoader()\n                            if (isMultiRow && keyTab.keyModel.totalRowCount === 0) {\n                                console.log(\"Load rows count\")\n                                keyTab.keyModel.loadRowsCount()\n                            } else {\n                                console.log(\"Load rows\")\n                                keyTab.keyModel.loadRows(currentStart, maxItemsOnPage)\n                            }\n                        }\n\n                        function resetCurrentRow() {\n                            table.currentRow = -1\n                        }\n\n                        function sort(role, order) {\n                            table.model.setSortRole(role)\n                            table.model.setSortOrder(order)\n                        }\n\n                        onRowsChanged: wrapper.hideLoader()\n                        onCurrentRowChanged: {\n                            console.log(\"Current row in table changed: \", currentRow)\n                            if (currentRow >= 0) {\n                                valueEditor.loadRowValue(currentStart + table.model.getOriginalRowIndex(currentRow))\n                            }\n                        }\n                    }\n                }\n            }\n\n            Loader {\n                id: filtersLoader\n\n                Layout.fillWidth: true\n                Layout.preferredHeight: 40\n                visible: status === Loader.Ready\n\n                source: keyModel && (keyType === \"list\" || keyType === \"stream\") ?\n                            \"./filters/\" + String(keyType)[0].toUpperCase()\n                            + String(keyType).substring(1) +\"Filters.qml\"  : \"\"\n            }\n\n        }\n\n        ValueTableActions {}\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/ValueTableActions.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls.Styles 1.1\nimport QtQuick.Window 2.2\nimport \"./editors/editor.js\" as Editor\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../common\"\nimport rdm.models 1.0\nimport Qt.labs.qmlmodels 1.0\n\nColumnLayout {\n    Layout.fillHeight: true\n    Layout.preferredWidth: 200\n    Layout.maximumWidth: 200\n    Layout.alignment: Qt.AlignTop\n    Layout.bottomMargin: 10\n\n    BetterButton {\n        objectName: \"rdm_value_tab_add_row_btn\"\n        Layout.fillWidth: true\n        text: qsTranslate(\"RESP\",\"Add Row\")\n        iconSource: PlatformUtils.getThemeIcon(\"add.svg\")\n        onClicked: {\n            addRowDialog.open()\n        }\n\n        BetterDialog {\n            id: addRowDialog\n            title: keyType === \"hyperloglog\"? qsTranslate(\"RESP\",\"Add Element to HLL\")\n                                            : qsTranslate(\"RESP\",\"Add Row\")\n\n            width: 550\n            height: 400\n\n            contentItem: Rectangle {\n                color: sysPalette.base\n                implicitWidth: 800\n                implicitHeight: PlatformUtils.isOSX()? 680 : 600\n\n                ColumnLayout {\n                    anchors.fill: parent\n                    anchors.margins: 10\n\n                    Loader {\n                        id: valueAddEditor\n\n                        Layout.fillWidth: true\n                        Layout.fillHeight: true\n\n                        property int currentRow: -1\n                        objectName: \"rdm_add_row_dialog\"\n\n                        source: keyTab.keyModel ? Editor.getEditorByTypeString(keyType, true) : \"\"\n\n                        onLoaded: {\n                            item.state = \"add\"                            \n                            item.initEmpty()\n                            keyTab.addRowDialog = addRowDialog\n                        }\n                    }\n                }\n            }\n\n            footer: BetterDialogButtonBox {\n                BetterButton {\n                    DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole\n                    objectName: \"rdb_add_row_dialog_add_button\"\n                    text: qsTranslate(\"RESP\",\"Add\")\n\n                    onClicked: {\n                        if (!valueAddEditor.item)\n                            return false\n\n                        valueAddEditor.item.getValue(true, function (valid, row){\n                            if (!valid) {\n                                return;\n                            }                            \n\n                            keyTab.keyModel.addRow(row)\n                            keyTab.keyModel.reload()\n                            valueAddEditor.item.reset()\n                            valueAddEditor.item.initEmpty()\n                            addRowDialog.close()\n                        });\n                    }\n                }\n\n                BetterButton {\n                    text: qsTranslate(\"RESP\", \"Cancel\")\n                    DialogButtonBox.buttonRole: DialogButtonBox.RejectRole\n                }\n            }\n\n            visible: false\n        }\n    }\n\n    BetterButton {\n        objectName: \"rdm_value_editor_delete_row_btn\"\n        Layout.fillWidth: true\n        text: qsTranslate(\"RESP\",\"Delete row\")\n        iconSource: PlatformUtils.getThemeIcon(\"delete.svg\")\n        enabled: table.currentRow != -1\n\n        onClicked: {\n            if (keyTab.keyModel.totalRowCount === 1) {\n                deleteRowConfirmation.text = qsTranslate(\"RESP\",\"The row is the last one in the key. After removing it key will be deleted.\")\n            } else {\n                deleteRowConfirmation.text = qsTranslate(\"RESP\",\"Do you really want to remove this row?\")\n            }\n\n            var rowIndex = table.currentStart + table.model.getOriginalRowIndex(table.currentRow)\n\n            console.log(\"removing row\", rowIndex)\n\n            deleteRowConfirmation.rowToDelete = rowIndex\n            deleteRowConfirmation.open()\n        }\n\n        BetterMessageDialog {\n            id: deleteRowConfirmation\n            title: qsTranslate(\"RESP\",\"Delete row\")\n            text: \"\"\n            onYesClicked: {\n                console.log(\"remove row in key\")\n                keyTab.keyModel.deleteRow(rowToDelete)\n                table.resetCurrentRow()\n                valueEditor.clear()\n                table.model.invalidate()\n            }\n            visible: false\n            property int rowToDelete\n        }\n\n    }\n\n    BetterButton {\n        objectName: \"rdm_value_editor_reload_value_btn\"\n        Layout.fillWidth: true\n        text: qsTranslate(\"RESP\",\"Reload Value\")\n        iconSource: PlatformUtils.getThemeIcon(\"refresh.svg\")\n        action: reLoadAction\n\n        Action {\n            id: reLoadAction\n            shortcut: StandardKey.Refresh\n            onTriggered: {\n                reloadValue()\n            }\n\n\n        }\n    }\n\n    RowLayout {\n        Layout.fillWidth: true\n\n        BetterTextField {\n            id: searchField\n\n            Layout.fillWidth: true\n\n            readOnly: keyTab.keyModel ? keyTab.keyModel.singlePageMode : false\n            placeholderText: qsTranslate(\"RESP\",\"Search on page...\")\n\n            Component.onCompleted: {\n                table.searchField = searchField\n            }\n        }\n\n        BetterButton {\n            id: clearGlobalSearch\n            visible: keyTab.keyModel ? keyTab.keyModel.singlePageMode : false\n\n            iconSource: PlatformUtils.getThemeIcon(\"clear.svg\")\n\n            onClicked: {\n                wrapper.showLoader()\n                searchField.text = \"\"\n                keyTab.keyModel.singlePageMode = false\n                reLoadAction.trigger()\n            }\n        }\n    }\n\n    BetterButton {\n        id: globalSearch\n\n        Layout.fillWidth: true\n        iconSource: PlatformUtils.getThemeIcon(\"loader.svg\")\n        text: qsTranslate(\"RESP\",\"Full Search\")\n\n        onClicked: {\n            wrapper.showLoader()\n            keyTab.keyModel.singlePageMode = true\n            keyTab.keyModel.loadRows(0, keyTab.keyModel.totalRowCount)\n        }\n    }\n\n    Item {\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n    }\n\n    Pagination {\n        id: pagination\n        Layout.fillWidth: true\n        visible: keyTab.keyModel ? isMultiRow : false\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/ValueTableCell.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Controls 2.0\n\nItem {\n    id: root\n\n    property alias text: textItem.text\n    property alias color: background.color\n    property bool selected: false\n\n    signal clicked\n\n    Rectangle {\n        id: background\n        anchors.fill: parent\n        border.color: root.selected ? sysPalette.midlight : sysPalette.mid\n        border.width: 1\n        color: root.selected ? sysPalette.highlight : sysPalette.base\n        clip: true\n\n        TextInput {\n            id: textItem\n            anchors.centerIn: parent\n            wrapMode: Text.WrapAnywhere\n            color: root.selected ? sysPalette.highlightedText : sysPalette.text            \n            readOnly: true\n            selectByMouse: true            \n            autoScroll: false\n        }\n    }\n\n    function renderText(t) {\n        if (t === \"\")\n            return t\n\n        if (qmlUtils.binaryStringLength(t) > 100) {\n            return qmlUtils.printable(t, false, 100) + \"...\"\n        }\n\n        return qmlUtils.printable(t)\n                + (textItem.lineCount > 1 ? '...' : '')\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        enabled: !root.selected\n\n        onClicked: {\n            root.clicked()\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/ValueTabs.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 2.13\nimport QtQuick.Controls.Styles 1.1\nimport QtQuick.Window 2.2\nimport \"./editors/editor.js\" as Editor\nimport \"./../common/platformutils.js\" as PlatformUtils\nimport \"./../common\"\nimport rdm.models 1.0\nimport Qt.labs.qmlmodels 1.0\n\nRepeater {\n\n    BetterTab {\n        id: keyTab\n        objectName: \"rdm_value_tab\"\n\n        Component {\n            id: valueTabButton\n\n            BetterTabButton {\n                objectName: \"rdm_value_tab_btn\"\n                icon.source: PlatformUtils.getThemeIcon(\"key.svg\")\n\n                text: tabName\n                tooltip: keyModel && tabName <= keyName? keyName : \"\"\n\n                onCloseClicked: {\n                    if (valueEditor.item && valueEditor.item.isEdited() && keyType != \"stream\") {\n                        closeConfirmation.open()\n                    } else {\n                        valuesModel.closeTab(keyIndex)\n                    }\n                }\n\n                BetterMessageDialog {\n                    id: closeConfirmation\n                    title: qsTranslate(\"RESP\",\"Changes are not saved\")\n                    text: qsTranslate(\"RESP\",\"Do you want to close key tab without saving changes?\")\n\n                    visible: false\n\n                    onYesClicked: {\n                        valuesModel.closeTab(keyIndex)\n                    }\n                }\n            }\n        }\n\n        property int tabIndex: keyIndex\n        property var table\n        property var valueEditor\n        property var searchModel\n        property var tabButton\n        property bool loadingModel: showLoader\n        property variant keyModel: keyViewModel\n        property var addRowDialog\n\n        onKeyModelChanged: {\n            console.log(\"keyModel changed\")\n            if (keyModel && keyModel.isLoaded) {\n                table.forceLoading = false\n                table.currentStart = 0\n                table.searchField.text = \"\"\n\n                if (valueEditor.item)\n                    valueEditor.item.reset()\n\n                table.loadValue()\n            }\n        }\n\n        property Component searchModelComponent: Component {\n            SortFilterProxyModel {\n                source: keyViewModel\n                sortOrder: Qt.AscendingOrder\n                sortCaseSensitivity: Qt.CaseInsensitive\n                sortRole: keyTab.keyModel && keyTab.keyModel.isLoaded ? \"row\" : \"\"\n\n                filterString: table.searchField.text\n                filterSyntax: SortFilterProxyModel.Wildcard\n                filterCaseSensitivity: Qt.CaseInsensitive\n                filterKeyColumn: -1\n\n                onFilterStringChanged: {\n                    table.resetCurrentRow()\n                }\n\n                Component.onCompleted:  {\n                    if (keyTab.keyModel && keyTab.keyModel.isLoaded && keyTab.keyModel.singlePageMode) {\n                        // NOTE(u_glide): disable live search in all values\n                        filterString = table.searchField.text\n                    }\n                }\n            }\n        }\n\n        Keys.onPressed: {\n            var reloadKey = event.key == Qt.Key_F5\n                    || (event.key == Qt.Key_R && (event.modifiers & Qt.ControlModifier))\n                    || (event.key == Qt.Key_R && (event.modifiers & Qt.MetaModifier))\n\n            if (reloadKey && keyModel.isLoaded) {\n                console.log(\"Reload\")\n                keyModel.reload()\n            }\n        }\n\n        Component.onCompleted: {\n            keyTab.focus = true\n            keyTab.forceActiveFocus()\n\n            // Update tabBar\n            if (!tabButton) {\n                tabButton = valueTabButton.createObject(keyTab);\n                tabButton.self = tabButton;\n                tabButton.tabRef = keyTab;\n                tabBar.addItem(tabButton)\n                tabBar.activateTabButton(tabButton)\n                tabs.activateTab(keyTab)\n            }\n        }\n\n        onActivate: {\n            valuesModel.setCurrentTab(keyIndex)\n        }\n\n        function reloadValue() {\n            console.log(\"Reload value in tab\")\n            keyTab.keyModel.reload()\n\n            if (isMultiRow) {\n                valueEditor.clear()\n                table.resetCurrentRow()\n\n                if (table.currentPage > table.totalPages) {\n                    table.goToPage(1)\n                }\n            }\n        }\n\n        Rectangle {\n            id: wrapper\n            color: sysPalette.base\n            anchors.fill: parent\n            anchors.margins: 5\n\n            function showLoader() {\n                uiBlocker.visible = true\n            }\n\n            function hideLoader() {\n                uiBlocker.visible = false\n            }\n\n            ColumnLayout {\n                visible: !loadingModel\n                anchors.fill: parent\n                spacing: 5\n\n                RowLayout {\n                    Layout.preferredHeight: 30\n                    Layout.minimumHeight: 30\n                    Layout.fillWidth: true\n                    spacing: 5\n\n                    BetterLabel {\n                        Layout.preferredWidth: isMultiRow ? 70 : 90\n                        text: keyModel? keyType.toUpperCase() + \":\" : \"\";\n                        font.bold: true\n                        horizontalAlignment: Text.AlignHCenter\n                    }\n\n                    BetterTextField {\n                        id: keyNameField\n                        Layout.fillWidth: true\n                        text: keyModel? keyName : \"\"\n                        readOnly: true\n                        objectName: \"rdm_key_name_field\"                    \n\n                        ImageButton {\n                            anchors.right: parent.right\n                            anchors.rightMargin: 5\n                            anchors.verticalCenter: parent.verticalCenter\n\n                            iconSource: PlatformUtils.getThemeIcon(\"cleanup.svg\")\n                            tooltip: qsTranslate(\"RESP\",\"Rename key\")\n                            objectName: \"rdm_key_rename_btn\"\n\n                            onClicked: renameConfirmation.open()\n\n                            BetterDialog {\n                                id: renameConfirmation\n                                title: qsTranslate(\"RESP\",\"Rename key\")\n\n                                width: 520\n\n                                RowLayout {\n                                    implicitWidth: 500\n                                    implicitHeight: 100\n                                    width: 500\n\n                                    BetterLabel { text: qsTranslate(\"RESP\",\"New name:\") }\n                                    BetterTextField {\n                                        id: newKeyName;\n                                        Layout.fillWidth: true;\n                                        objectName: \"rdm_rename_key_field\"\n                                        text: keyModel? keyName : \"\"\n                                    }\n                                }\n\n                                onAccepted: {\n                                    if (newKeyName.text.length == 0) {\n                                        return open()\n                                    }\n\n                                    keyTab.keyModel.renameKey(newKeyName.text)\n                                }\n\n                                visible: false\n                            }\n                        }\n                    }\n\n                    BetterLabel {\n                        visible: keyType === \"hyperloglog\";\n                        text:  qsTranslate(\"RESP\",\"Size: \") + keyRowsCount\n                    }\n\n                    BetterButton {\n                        Layout.preferredWidth: isMultiRow? 92 : 98\n\n                        text: qsTranslate(\"RESP\",\"TTL:\") + keyTtl\n                        objectName: \"rdm_key_ttl_value\"\n                        tooltip: keyTtl\n\n                        BetterDialog {\n                            id: setTTLConfirmation\n                            title: qsTranslate(\"RESP\",\"Set key TTL\")\n\n                            width: 520\n\n                            RowLayout {\n                                implicitWidth: 500\n                                implicitHeight: 100\n                                width: 500\n\n                                BetterLabel { text: qsTranslate(\"RESP\",\"New TTL:\") }\n                                BetterTextField {\n                                    id: newTTL;\n                                    Layout.fillWidth: true;\n                                    objectName: \"rdm_set_ttl_key_field\"\n                                    inputMethodHints: Qt.ImhDigitsOnly\n                                    validator: IntValidator{bottom: 1}\n                                }\n                            }\n\n                            footer: BetterDialogButtonBox {\n                                BetterButton {\n                                    text: qsTranslate(\"RESP\",\"Save\")\n                                    DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole\n                                }\n\n                                BetterButton {\n                                    objectName: \"rdm_persist_key_btn\"\n                                    text: qsTranslate(\"RESP\",\"Persist key\")\n                                    onClicked: {\n                                        keyTab.keyModel.persistKey()\n                                        setTTLConfirmation.close()\n                                    }\n                                }\n\n                                BetterButton {\n                                    text: qsTranslate(\"RESP\",\"Cancel\")\n                                    onClicked: setTTLConfirmation.close()\n                                }\n                            }\n\n                            onAccepted: {\n                                if (newTTL.text.length == 0) {\n                                    return open()\n                                }\n\n                                keyTab.keyModel.setTTL(newTTL.text)\n                            }\n\n                            visible: false\n                        }\n\n                        onClicked: {\n                            if (keyTtl > 0) {\n                                newTTL.text = \"\"+keyTtl\n                            } else {\n                                newTTL.text = \"\"\n                            }\n\n                            setTTLConfirmation.open()\n                        }\n                    }\n\n                    BetterButton {\n                        objectName: \"rdm_value_tab_delete_btn\"\n                        Layout.preferredWidth: 98\n                        text: qsTranslate(\"RESP\",\"Delete\")\n                        iconSource: PlatformUtils.getThemeIcon(\"delete.svg\")\n\n                        BetterMessageDialog {\n                            id: deleteConfirmation\n                            title: qsTranslate(\"RESP\",\"Delete key\")\n                            text: qsTranslate(\"RESP\",\"Do you really want to delete this key?\")\n                            onYesClicked: {\n                                keyTab.keyModel.removeKey()\n                            }\n                            visible: false\n                        }\n\n                        onClicked: {\n                            deleteConfirmation.open()\n                        }\n                    }\n\n                    BetterButton {\n                        objectName: \"rdm_value_editor_reload_value_btn\"\n                        text: qsTranslate(\"RESP\",\"Reload Value\")\n                        onClicked: reloadValue()\n                        visible: !isMultiRow\n                        iconSource: PlatformUtils.getThemeIcon(\"refresh.svg\")\n                    }\n                }\n\n                BetterSplitView {\n                    orientation: Qt.Vertical\n                    Layout.fillHeight: true\n                    Layout.fillWidth: true\n\n                    // Table\n                    ValueTable {\n                        id: navigationTable\n\n                        Layout.fillWidth: true\n                        Layout.fillHeight: false\n                        Layout.bottomMargin: 20\n                        SplitView.minimumHeight: 300\n                        visible: keyModel? isMultiRow : false\n                    }\n\n                    // Value editor\n                    Item {\n                        id: editorWrapper\n\n                        SplitView.fillWidth: true\n                        SplitView.fillHeight: !isMultiRow\n                        Layout.topMargin: 20\n                        SplitView.minimumHeight: 220\n\n                        BetterDialog {\n                            id: fullScreenEditorDialog\n                            title: tabName\n\n                            width: approot.width * 0.9\n                            height: approot.height * 0.9\n                            footer: null\n\n                            Rectangle {\n                                id: fullscreenEditorParent\n\n                                anchors.fill: parent\n                                implicitHeight: 500\n                                implicitWidth: 800\n                            }\n\n                            onClosed: {\n                                editor.state = \"default\"\n                            }\n                        }\n\n                        Rectangle {\n                            id: editor\n                            anchors.fill: parent\n\n                            color: sysPalette.base\n\n                            state: \"default\"\n\n                            states: [\n                                State {\n                                        name: \"full_screen\"\n                                        ParentChange { target: editor; parent: fullscreenEditorParent;}\n                                        PropertyChanges {\n                                            target: fullScreenEditorDialog\n                                            visible: true\n                                        }\n                                        PropertyChanges {\n                                            target: fullScreenModeBtn\n                                            visible: false\n                                        }\n                                    },\n                                State {\n                                    name: \"default\"\n                                    ParentChange { target: editor; parent: editorWrapper; }\n                                    PropertyChanges {\n                                        target: fullScreenEditorDialog\n                                        visible: false\n                                    }\n                                    PropertyChanges {\n                                        target: fullScreenModeBtn\n                                        visible: true\n                                    }\n                                }\n                            ]                            \n\n                            ColumnLayout {\n                                id: editorLayout\n                                anchors.fill: parent\n                                anchors.margins: 5                                \n                                spacing: 10\n\n                                Loader {\n                                    id: valueEditor\n                                    objectName: \"rdm_value_editor_loader\"\n\n                                    Layout.topMargin: 5\n                                    Layout.fillWidth: true\n                                    Layout.fillHeight: true\n                                    Layout.minimumHeight: 180\n\n                                    Component.onCompleted: {\n                                        keyTab.valueEditor = valueEditor\n                                    }\n\n                                    property int currentRow: -1\n\n                                    source: keyTab.keyModel? Editor.getEditorByTypeString(keyType, false) : \"\"\n\n                                    function loadRowValue(row) {\n                                        console.log(\"loading row value\", row)\n                                        if (valueEditor.item) {\n                                            var rowValue = keyTab.keyModel.getRow(row)\n                                            valueEditor.currentRow = row\n                                            valueEditor.item.reset()\n                                            valueEditor.item.defaultFormatter = defaultFormatter\n                                            valueEditor.item.setValue(rowValue)\n                                        } else {\n                                            console.log(\"cannot load row value - item is missing\")\n                                        }\n                                    }\n\n                                    function clear() {\n                                        if (valueEditor.item) {\n                                            currentRow = -1\n                                            valueEditor.item.keyType = Qt.binding(function() { return keyType });\n                                            valueEditor.item.reset()\n                                        }\n                                    }\n\n                                    onLoaded: clear()\n                                }\n                            }\n                        }\n                    }\n                    // Value editor end\n                }\n            }\n\n            Rectangle {\n                id: uiBlocker\n                visible: loadingModel\n                anchors.fill: parent\n                color: loadingModel? Qt.rgba(0, 0, 0, 0) : Qt.rgba(0, 0, 0, 0.1)\n\n                Item {\n                    anchors.fill: parent\n\n                    ColumnLayout {\n                        anchors.centerIn: parent;\n\n                        BusyIndicator { Layout.alignment: Qt.AlignHCenter;  running: true }\n\n                        BetterLabel {\n                            visible: loadingModel\n                            text: tabName\n                        }\n                    }\n                }\n\n                MouseArea {\n                    anchors.fill: parent\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/AbstractEditor.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.2\nimport QtQuick.Controls.Styles 1.1\n\nColumnLayout {\n\n    state: \"edit\"\n    property string keyType: \"\"\n\n    states: [\n        State { name: \"new\"}, // Creating new key\n        State { name: \"add\"}, // Adding new value to existing key\n        State { name: \"edit\"} // Editing existing key\n    ]\n\n    function initEmpty() {\n        console.exception(\"Not implemented\")\n    }\n\n    function getValue(validateVal, callback) {\n        console.exception(\"Not implemented\")\n    }\n\n    function isEdited() {\n        console.exception(\"Not implemented\")\n    }\n\n    function setValue(value) {\n        console.exception(\"Not implemented\")\n    }\n\n    function reset() {\n        console.exception(\"Not implemented\")\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/HashItemEditor.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.2\nimport QtQuick.Controls.Styles 1.1\n\nimport \".\"\n\nAbstractEditor {\n    id: root\n    anchors.fill: parent    \n\n    property bool active: false\n    property alias defaultFormatter: textArea.defaultFormatter\n\n    MultilineEditor {\n        id: keyText\n        fieldLabel: qsTranslate(\"RESP\",\"Key:\")\n        Layout.fillWidth: true\n        Layout.minimumHeight: 30\n        Layout.preferredHeight: root.state == \"new\"? 70: 140\n\n        value: \"\"\n        enabled: root.active || root.state !== \"edit\"\n        showToolBar: root.state == \"edit\"\n        showSaveBtn: root.state == \"edit\"\n        showFormatters: root.state == \"edit\"\n        objectName: \"rdm_key_hash_key_field\"\n        formatterSettingsPrefix: \"hash_key_\"\n    }\n\n    MultilineEditor {\n        id: textArea\n        Layout.fillWidth: true\n        Layout.fillHeight: true        \n        enabled: root.active || root.state !== \"edit\"\n        showToolBar: root.state == \"edit\"\n        showFormatters: root.state == \"edit\"\n        objectName: \"rdm_key_hash_text_field\"\n\n        function validationRule(raw) {\n            return true;\n        }\n    }\n\n    function initEmpty() {\n        keyText.initEmpty()\n        textArea.initEmpty()\n    }\n\n    function getValue(validateVal, callback) {\n        keyText.validate(function (keyTextValid, rawKey) {\n            if (!validateVal) {\n                return callback(keyTextValid, {\"value\": \"\", \"key\": rawKey});\n            } else {\n                textArea.validate(function (textAreaValid, rawValue) {\n                    return callback(keyTextValid && textAreaValid, {\"value\": rawValue, \"key\": rawKey});\n                });\n            }\n        });\n    }\n\n    function setValue(rowValue) {\n        if (!rowValue)\n            return       \n\n        active = true\n        keyText.loadFormattedValue(rowValue['key'])\n        textArea.loadFormattedValue(rowValue['value'])\n    }\n\n    function isEdited() {\n        return textArea.isEdited || keyText.isEdited\n    }    \n\n    function reset() {\n        textArea.reset()\n        keyText.reset()\n        active = false\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/MultilineEditor.qml",
    "content": "import QtQuick 2.5\nimport QtQuick.Controls 2.13\nimport QtQuick.Layouts 1.1\nimport QtQuick.Window 2.2\nimport Qt.labs.settings 1.0\nimport rdm.models 1.0\nimport \"../../common/\"\nimport \"../../common/platformutils.js\" as PlatformUtils\n\nItem\n{\n    id: root\n\n    property bool enabled\n    property string textColor\n    property int imgBtnWidth: PlatformUtils.isOSXRetina(Screen)? 18 : 22\n    property int imgBtnHeight: PlatformUtils.isOSXRetina(Screen)? 18 : 22\n    property bool showToolBar: false\n    property bool showSaveBtn: false\n    property bool showFormatters: true\n    property bool showOnlyRWformatters: false\n    property bool showValueSize: true\n    property string fieldLabel: qsTranslate(\"RESP\",\"Value\") + \":\"\n    property bool isEdited: false\n    property var value    \n    property int valueCompression: 0\n    property alias readOnly: textView.readOnly\n    property string formatterSettingsPrefix: \"\"\n    property string lastSelectedFormatterSetting: \"last_selected_\" + root.formatterSettingsPrefix + \"formatter\"\n    property string lastSelectedManualDecompression: \"last_selected_\" + root.formatterSettingsPrefix + \"decompression\"\n    property string defaultFormatter: \"auto\"\n\n    property var __formatterCombobox: formatterSelector\n    property var __textView: textView\n\n    function __getFormattingContext()\n    {\n        return {\n            \"redis-key-name\": root.parent.state === \"new\"? newKeyName.value : keyName,\n            \"redis-key-type\": keyType,\n        }\n    }\n\n    function initEmpty() {\n        // init editor with empty model\n        textView.model = qmlUtils.wrapLargeText(\"\")\n        textView.readOnly = false\n        textView.textFormat = TextEdit.PlainText\n    }\n\n    function validationRule(raw)\n    {\n        return qmlUtils.binaryStringLength(raw) > 0\n    }\n\n    function validate(callback) {\n        loadRawValue(function (error, raw) {\n\n            if (error) {\n                notification.showError(error)\n                return callback(false, raw);\n            }\n\n            var valid = validationRule(raw)            \n\n            if (valid) {\n                hideValidationError()\n            } else {\n                showValidationError(qsTranslate(\"RESP\", \"Enter valid value\"))\n            }\n\n            return callback(valid, raw)\n        });\n    }\n\n    function compress(val) {\n        if (valueCompression > 0) {\n            return qmlUtils.compress(val, valueCompression)\n        } else {\n            return val\n        }\n    }\n\n    function loadRawValue(callback) {\n        function process(formattedValue) {\n            var formatter = valueFormattersModel.get(formatterSelector.currentIndex)\n\n             formatter.getRaw(formattedValue, function (error, raw) {                 \n                 var compressed = compress(raw);\n                 return callback(error, compressed)\n             }, __getFormattingContext())\n        }\n\n        if (textView.format === \"json\") {\n            formatterSelector.model.getJSONFormatter().getRaw(textView.model.getText(), function (jsonError, plainText) {\n                if (jsonError) {\n                    return callback(jsonError, \"\")\n                }\n\n                process(plainText)\n            }, __getFormattingContext())\n        } else {\n            process(textView.model.getText())\n        }\n    }\n\n    function loadFormattedValue(val) {\n\n        var guessFormatter = false;\n\n        if (val) {\n            root.value = val;\n            guessFormatter = true;\n        }\n\n        var isBin = qmlUtils.isBinaryString(root.value)\n        binaryFlag.visible = isBin\n\n        if (isBin && qmlUtils.binaryStringLength(root.value) > appSettings.valueSizeLimit) {\n            largeValueDialog.visible = true;\n            root.showFormatters = false;\n            textView.model = qmlUtils.wrapLargeText(qmlUtils.printable(root.value, false, 50000));\n            textView.readOnly = true;\n            saveBtn.enabled = false;\n            return;\n        } else {\n            largeValueDialog.visible = false\n            root.showFormatters = true\n        }\n\n        var continueFormatting = function (guessFormatter) {\n            if (!root.value) {\n                console.log(\"Empty value. Skipping formatting stage\");\n                return;\n            }\n\n            // NOTE(u_glide): -1 means \"not set\", 0 - unknown or not compressed\n            if (valueCompression < 0) {\n                var compressionMethod = qmlUtils.isCompressed(root.value);\n\n                if (compressionMethod > 0) {\n                    valueCompression = compressionMethod\n                    root.value = qmlUtils.decompress(root.value, valueCompression)\n                    isBin = qmlUtils.isBinaryString(root.value)\n\n                    var compression = qmlUtils.compressionAlgName(valueCompression);\n\n                    // NOTE(u_glide): hint PHP formatter if MAGENTO/PHP compression detected\n                    if (guessFormatter && compression\n                            && compression.startsWith(\"magento-session-\")) {\n                        formatterSelector._select(\"php\");\n                        guessFormatter = false;\n                    }\n                }\n\n                // NOTE(u_glide): try to decompress using last \"no magic\" compression\n                if (isBin && valueCompression < 0\n                        && qmlUtils.binaryStringLength(root.value) <= appSettings.valueSizeLimit) {\n                    noMagicCompressionSelector.loadLastUsed()\n                }\n            }\n\n            // If current formatter is plain text - try to guess formatter\n            if (guessFormatter) {\n                _guessFormatter(root.value, isBin, function() {\n                    _loadFormatter(isBin)\n                })\n            } else {\n                _loadFormatter(isBin)\n            }\n        };\n\n        if (guessFormatter) {\n            console.log(\"Default formatter:\", root.defaultFormatter)\n\n            var formatterOverride = defaultFormatterSettings.value(\n                root.formatterSettingsPrefix + keyName, \"\"\n            );\n\n            if (!formatterOverride) {\n                if (root.defaultFormatter == \"last_used\") {\n                    formatterOverride = defaultFormatterSettings.value(root.lastSelectedFormatterSetting, \"\");\n                } else if (root.defaultFormatter != \"auto\") {\n                    formatterOverride = root.defaultFormatter;\n                }\n\n                if (!formatterOverride || root.defaultFormatter == \"auto\") {\n                    return continueFormatting(true)\n                }\n            }\n\n            var expectedFormatter = formatterSelector.find(formatterOverride);\n\n            if (expectedFormatter === -1) {\n                console.log(\"Formatter\", formatterOverride, \" is not loaded. Fallback to guessing...\")\n                return continueFormatting(true)\n            }\n\n            console.log(\"Formatter override:\", formatterOverride)\n\n            var cFormatter = formatterSelector.model.get(expectedFormatter)\n\n            return cFormatter.isValid(root.value, function (isValid) {\n                var compressionMethod = qmlUtils.isCompressed(root.value);\n\n                if (isValid || compressionMethod > 0) {\n                    formatterSelector._select(formatterOverride)\n                    continueFormatting(false)\n                } else {\n                    console.log(\"Formatter\", formatterOverride, \" cannot decode value. Fallback to guessing...\")\n                    continueFormatting(true)\n                }\n            }, __getFormattingContext())\n        } else {\n            continueFormatting(false)\n        }\n    }\n\n    function hintFormatter(name) {\n        if (showOnlyRWformatters) {\n            rwFormatterSelector._select(name)\n            formatterSelector._select(name)\n        } else {\n            formatterSelector._select(name)\n        }\n        _loadFormatter(false)\n    }\n\n    function _guessFormatter(value, isBin, callback) {\n        console.log(\"Guessing formatter\")\n\n        var candidates = valueFormattersModel.guessFormatter(value, isBin)\n\n        console.log(\"candidates:\", candidates)\n\n        if (Array.isArray(candidates)) {\n\n            for (var index in candidates) {\n                var cFormatter = formatterSelector.model[candidates[index]]\n\n                cFormatter.isValid(root.value, function (isValid) {\n                    if (isValid) {\n                        formatterSelector.currentIndex = candidates[index]\n                        callback()\n                    }\n                }, __getFormattingContext())\n\n                if (formatterSelector.currentIndex !== 0)\n                    break\n            }\n        } else {\n            formatterSelector.currentIndex = candidates\n            callback()\n        }\n    }\n\n    function _loadFormatter(isBin) {\n        if (!(0 <= formatterSelector.currentIndex\n              && formatterSelector.currentIndex < formatterSelector.count)) {\n            formatterSelector.currentIndex = formatterSelector.model.getDefaultFormatter(isBin)\n        }\n\n        var formatter = formatterSelector.model.get(formatterSelector.currentIndex)\n\n        uiBlocker.visible = true\n\n        function processFormattingResult(error, formatted, isReadOnly, format) {\n            textView.textFormat = (format === \"html\")\n                ? TextEdit.RichText\n                : TextEdit.PlainText;\n\n            console.log(\"format\", format)\n\n            if (error || (!formatted && root.value)) {\n                if (formatted) {\n                    textView.model = qmlUtils.wrapLargeText(formatted)\n                } else if (!error) {\n                    formatterSelector.currentIndex = valueFormattersModel.guessFormatter(root.value, isBin)\n                    return _loadFormatter(isBin)\n                }\n                textView.readOnly = isReadOnly\n                textView.format = \"text\"\n                root.isEdited = false\n                uiBlocker.visible = false\n\n                var details\n                if (error.length > 200) {\n                    details = error\n                    error = qsTranslate(\"RESP\",\"Formatting error\")\n                } else {\n                    details = \"\"\n                }\n\n                notification.showError(error || qsTranslate(\"RESP\",\"Unknown formatter error (Empty response)\"), details)\n                return\n            }\n\n            if (format === \"image\") {\n                imageView.source = formatted;\n            } else {\n                textView.model = qmlUtils.wrapLargeText(formatted)\n            }\n            textView.readOnly = isReadOnly\n            textView.format = format\n            root.isEdited = false\n            uiBlocker.visible = false\n        }\n\n        formatter.getFormatted(root.value, function (error, formatted, isReadOnly, format) {\n            textView.format = format\n\n            if (format === \"json\" && formatter[\"name\"] !== \"JSON\" && !error) {\n                formatterSelector.model.getJSONFormatter().getFormatted(formatted, function (jsonError, plainText) {\n                    if (jsonError) {\n                        processFormattingResult(jsonError, formatted, isReadOnly, format)\n                    } else {\n                        processFormattingResult(jsonError, plainText, isReadOnly, format)\n                    }\n                }, __getFormattingContext())\n            } else {\n                processFormattingResult(error, formatted, isReadOnly, format)\n            }\n        }, __getFormattingContext())\n    }\n\n    function reset() {\n        if (textView.model)\n            textView.model.cleanUp()\n\n        if (textView.model) {\n            qmlUtils.deleteTextWrapper(textView.model)\n        }\n\n        textView.model = null\n        root.value = \"\"\n        root.isEdited = false\n        root.valueCompression = -1\n        binaryFlag.visible = false\n        saveBtnTimer.resetSaveBtn()\n        hideValidationError()\n    }\n\n    function showValidationError(msg) {\n        validationError.text = msg\n        validationError.visible = true\n    }\n\n    function hideValidationError() {\n        validationError.visible = false\n    }\n\n    ColumnLayout {\n        anchors.fill: parent\n\n        RowLayout {\n            Layout.fillWidth: true\n            spacing: 5\n\n            BetterLabel { text: root.fieldLabel }\n            TextEdit {                \n                text: qsTranslate(\"RESP\", \"Size: \") + qmlUtils.humanSize(qmlUtils.binaryStringLength(value));\n                readOnly: true;\n                selectByMouse: true\n                color: \"#ccc\"\n                visible: showValueSize\n            }\n            BetterLabel { id: binaryFlag; text: qsTranslate(\"RESP\",\"[Binary]\"); visible: false; color: \"green\"; }\n            Item { Layout.fillWidth: true }\n\n            BetterLabel { visible: showFormatters; text: qsTranslate(\"RESP\",\"View as:\") }\n\n            BetterComboBox {\n                id: formatterSelector\n                visible: showFormatters && !showOnlyRWformatters\n                width: 200\n                model: valueFormattersModel\n                textRole: \"name\"\n                objectName: \"rdm_value_editor_formatter_combobox\"\n\n                onActivated: {\n                    currentIndex = index                    \n                    console.log(\"Set default formatter '\" + currentText + \"' for key \" + keyName)\n                    defaultFormatterSettings.setValue(root.formatterSettingsPrefix + keyName, currentText)\n                    defaultFormatterSettings.setValue(root.lastSelectedFormatterSetting, currentText)\n                    loadFormattedValue()\n                }\n            }\n\n            BetterComboBox {\n                id: rwFormatterSelector\n                visible: showFormatters && showOnlyRWformatters\n                width: 200\n                model: valueFormattersModel.rwFormatters\n                textRole: \"name\"\n                objectName: \"rdm_value_editor_rw_formatter_combobox\"\n\n                onActivated: {\n                    formatterSelector.currentIndex = valueFormattersModel.getFormatterIndex(currentText);\n                }\n            }\n\n            BetterLabel {\n                visible: noMagicCompressionSelector.visible\n                text: noMagicCompressionSelector.enabled? qsTranslate(\"RESP\",\"Try to decompress:\") :\n                                                          qsTranslate(\"RESP\",\"Decompressed:\")\n            }\n\n            BetterComboBox {\n                id: noMagicCompressionSelector\n\n                Layout.preferredWidth: 120\n                Layout.fillWidth: true\n\n                objectName: \"rdm_value_editor_compression_combobox\"\n                textRole: \"text\"\n\n                visible: {\n                    console.log(\"keyType:\", keyType)\n                    return binaryFlag.visible && keyType != \"hyperloglog\" && qmlUtils.binaryStringLength(root.value) <= appSettings.valueSizeLimit\n                            || root.valueCompression > 0\n                }\n\n                onEnabledChanged: {\n                    indicator.visible = enabled;\n                }\n\n                flat: !enabled\n\n                enabled: {\n                    return binaryFlag.visible && root.valueCompression < 1\n                            || root.valueCompression >= firstNoMagicMethod;\n                }\n\n                displayText: {\n                    if (0 < root.valueCompression && root.valueCompression < noMagicCompressionSelector.firstNoMagicMethod) {\n                        return qmlUtils.compressionAlgName(root.valueCompression)\n                    } else {\n                        return currentText;\n                    }\n                }\n\n                property int firstNoMagicMethod: {\n                    var noMagicCompress = qmlUtils.compressionMethodsNoMagic();\n                    return noMagicCompress[noMagicCompress.length - 1];\n                }\n\n                model: {\n                   var noMagicCompress = qmlUtils.compressionMethodsNoMagic();\n\n                   var modelList = [];\n                    for (var index in noMagicCompress) {\n                        var label = qmlUtils.compressionAlgName(noMagicCompress[index]);\n\n                        if (label === \"unknown\") {\n                            label = \"\";\n                        }\n\n                        modelList.push({\"value\": noMagicCompress[index], \"text\": label});\n                    }\n\n                   return modelList;\n                }\n\n                function loadLastUsed() {\n                    var lastSelected = defaultCompressionSettings.value(\n                                root.formatterSettingsPrefix + keyName,\n                                defaultCompressionSettings.value(root.lastSelectedManualDecompression, \"\")\n                    );                    \n\n                    selectItem(lastSelected);\n                }\n\n                onActivated: {\n                    console.log(\"Try to decompress as\", currentText)\n\n                    var expectedCompression = model[currentIndex]['value'];\n\n                    if (expectedCompression == 0) {\n                        valueCompression = 0\n                        defaultCompressionSettings.setValue(root.formatterSettingsPrefix + keyName, \"\")\n                        defaultCompressionSettings.setValue(root.lastSelectedManualDecompression, \"\")                        \n                        root.loadFormattedValue()\n                        return\n                    }\n\n                    var decompressed = qmlUtils.decompress(root.value, expectedCompression)\n\n                    if (qmlUtils.binaryStringLength(decompressed) > 0) {\n                        binaryFlag.visible = qmlUtils.isBinaryString(root.value)\n                        valueCompression = expectedCompression;\n                        root.loadFormattedValue(decompressed)\n                        defaultCompressionSettings.setValue(root.formatterSettingsPrefix + keyName, currentText)\n                        defaultCompressionSettings.setValue(root.lastSelectedManualDecompression, currentText)\n                        noMagicCompressionSelector.enabled = false;\n                    } else {\n                        notification.showError(qsTranslate(\"RESP\",\"Cannot decompress value using \") + currentText)\n                        defaultCompressionSettings.setValue(root.formatterSettingsPrefix + keyName, \"\")\n                        defaultCompressionSettings.setValue(root.lastSelectedManualDecompression, \"\")\n                        valueCompression = 0\n                        currentIndex = 0;\n                    }\n                }\n            }\n\n            BetterLabel {\n                visible: !showFormatters && qmlUtils.binaryStringLength(root.value) > appSettings.valueSizeLimit\n                text: qsTranslate(\"RESP\",\"Large value (>150kB). Formatters are not available.\")\n                color: \"red\"\n            }\n\n            BetterButton {\n                iconSource: PlatformUtils.getThemeIcon(\"add.svg\")\n                Layout.alignment: Qt.AlignHCenter\n\n                text: qsTranslate(\"RESP\",\"Add Element\");\n                visible: (keyType === \"hyperloglog\"\n                          || keyType === \"bf\"\n                          || keyType === \"cf\")\n\n                onClicked: {\n                    keyTab.addRowDialog.open()\n                }\n            }\n\n            RowLayout {\n                id: valueEditorToolBar\n                Layout.preferredWidth: isMultiRow ? 200 : 208\n                Layout.maximumWidth: isMultiRow ? 200 : 208\n\n                visible: showToolBar\n\n                RowLayout {\n                    Layout.fillWidth: true\n                    Layout.preferredWidth: 98\n\n                    ImageButton {\n                        id: copyValueToClipboardBtn\n                        iconSource: PlatformUtils.getThemeIcon(\"copy_2.svg\")\n                        implicitWidth: imgBtnWidth\n                        implicitHeight: imgBtnHeight\n                        imgWidth: imgBtnWidth\n                        imgHeight: imgBtnHeight\n\n                        Layout.alignment: Qt.AlignHCenter\n\n                        tooltip: qsTranslate(\"RESP\",\"Copy to Clipboard\")\n                        enabled: root.value !== \"\"\n\n                        onClicked: copyValue()\n\n                        function copyValue() {\n                            if (value) {\n                                qmlUtils.copyToClipboard(textView.model.getText())\n                            }\n                        }\n                    }\n\n                    SaveToFileButton {\n                        id: saveAsBtn\n                        objectName: \"rdm_save_value_to_file_btn\"\n\n                        Layout.alignment: Qt.AlignHCenter\n\n                        implicitWidth: imgBtnWidth\n                        implicitHeight: imgBtnHeight\n                        imgWidth: imgBtnWidth\n                        imgHeight: imgBtnHeight\n\n                        enabled: root.value !== \"\" && root.showFormatters\n\n                        shortcutText: qmlUtils.standardKeyToString(StandardKey.SaveAs)\n                    }\n\n                    SaveToFileButton {\n                        id: saveAsRawBtn\n                        objectName: \"rdm_save_raw_value_to_file_btn\"\n\n                        raw: true\n\n                        Layout.alignment: Qt.AlignHCenter\n\n                        implicitWidth: imgBtnWidth\n                        implicitHeight: imgBtnHeight\n                        imgWidth: imgBtnWidth\n                        imgHeight: imgBtnHeight\n\n                        enabled: root.value !== \"\"\n                    }\n                }\n\n                ImageButton {\n                    id: fullScreenModeBtn\n\n                    iconSource: editor.state === \"default\"?\n                                    PlatformUtils.getThemeIcon(\"maximize.svg\")\n                                  : PlatformUtils.getThemeIcon(\"minimize.svg\")\n                    implicitWidth: imgBtnWidth\n                    implicitHeight: imgBtnHeight\n                    imgWidth: imgBtnWidth * 0.8\n                    imgHeight: imgBtnHeight * 0.8\n\n                    tooltip: (editor.state === \"default\"? \"\" : qsTranslate(\"RESP\",\"Exit \"))\n                                                         + qsTranslate(\"RESP\",\"Full Screen Mode\")\n\n                    onClicked: {\n                        editor.state = editor.state === \"default\"? \"full_screen\" : \"default\"\n                        editor.forceActiveFocus()\n                    }\n                }\n\n                BetterButton {\n                    id: saveBtn\n                    objectName: \"rdm_value_editor_save_btn\"\n\n                    state: \"default\"\n                    implicitWidth: isMultiRow ? 100 : 105\n\n                    text: qsTranslate(\"RESP\",\"Save\")\n                    tooltip: qsTranslate(\"RESP\",\"Save Changes\") + \" (\" + shortcutText + \")\"\n                    visible: showSaveBtn\n\n                    property string shortcutText: qmlUtils.standardKeyToString(StandardKey.Save)\n\n                    onClicked: saveChanges()\n\n                    function saveChanges() {\n                        if (!valueEditor.item || !valueEditor.item.isEdited()) {\n                            return\n                        }\n\n                        valueEditor.item.getValue(true, function (valid, row) {\n                            if (!valid)\n                                return;\n\n                            saveBtnTimer.start()                            \n                            keyTab.keyModel.updateRow(valueEditor.currentRow, row)\n                        })\n                    }\n\n                    states: [\n                        State {\n                            name: \"default\"\n\n                            PropertyChanges {\n                                target: saveBtn\n                                iconSource: PlatformUtils.getThemeIcon(\"save.svg\")\n                                enabled: !showOnlyRWformatters && root.value !== \"\" && valueEditor.item.isEdited() && keyType != \"stream\"\n                            }\n                        },\n                        State {\n                            name: \"saving\"\n\n                            PropertyChanges {\n                                target: saveBtn\n                                iconSource: PlatformUtils.getThemeIcon(\"wait.svg\")\n                                enabled: false\n                            }\n                        }\n                    ]\n\n                    Connections {\n                        target: keyTab.keyModel ? keyTab.keyModel : null\n\n                        function onValueUpdated() {\n                            root.isEdited = false\n                            saveBtnTimer.resetSaveBtn()\n                        }\n\n                        function onError() { saveBtnTimer.resetSaveBtn() }\n                    }\n\n                    Timer {\n                        id: saveBtnTimer\n                        interval: 500\n                        repeat: true\n                        triggeredOnStart: true\n                        onTriggered: saveBtn.state = \"saving\"\n\n                        function resetSaveBtn() {\n                            saveBtnTimer.stop()\n                            saveBtn.state = \"default\"\n                        }\n                    }\n                }\n\n                Item {\n                    width: 100\n                    visible: !showSaveBtn && keyType == \"hash\"\n                }\n            }\n\n        }\n\n        Rectangle {\n            id: searchToolbar\n\n            property int lastSearchResultPosition: -1\n\n            color: sysPalette.base\n            border.color: sysPalette.mid\n            border.width: 1\n\n            Layout.fillWidth: true\n            Layout.preferredHeight: 50\n            visible: false\n\n            RowLayout {\n                anchors.fill: parent\n                anchors.rightMargin: 10\n                anchors.leftMargin: 10\n\n                Image {\n                    source: PlatformUtils.getThemeIcon(\"search.svg\")\n                    Layout.preferredWidth: 20\n                    Layout.preferredHeight: 20\n                }\n\n                BetterTextField {\n                    id: searchField\n                    objectName: \"rdm_value_editor_search_field\"\n                    placeholderText: qsTranslate(\"RESP\", \"Search string\")\n\n                    onTextChanged: {\n                        searchToolbar.lastSearchResultPosition = -1;\n                        noResults.visible = false;\n                    }\n\n                    onAccepted: submitSearchButton.performSearch()\n\n                    Layout.preferredWidth: 300\n                }\n                BetterButton {\n                    id: submitSearchButton\n                    objectName: \"rdm_value_editor_search_btn\"\n                    text: searchToolbar.lastSearchResultPosition>=0 ? qsTranslate(\"RESP\",\"Find Next\") : qsTranslate(\"RESP\",\"Find\")\n                    onClicked: {\n                        performSearch()\n                    }\n\n                    function performSearch() {\n                        noResults.visible = false;\n\n                        var result = textView.model.searchText(searchField.text,\n                                                               searchToolbar.lastSearchResultPosition,\n                                                               searchRegexInText.checked)                        \n\n                        if (result[0] >= 0) {\n                            textView.currentIndex = result[0];\n                            searchToolbar.lastSearchResultPosition = result[1] + result[3];\n                            textView.currentItem.selectSearchResult(result[2], result[3], searchField.text);\n                        } else {\n                            noResults.text = searchToolbar.lastSearchResultPosition>=0 ? qsTranslate(\"RESP\",\"Cannot find more results\")\n                                                                                       : qsTranslate(\"RESP\",\"Cannot find any results\");\n                            if (searchToolbar.lastSearchResultPosition>=0) {\n                                searchToolbar.lastSearchResultPosition = -1;\n                            }\n\n                            noResults.visible = true;\n                        }\n                    }\n                }\n\n                BetterCheckbox {\n                    id: searchRegexInText\n                    objectName: \"rdm_value_editor_search_regex_checkbox\"\n                    text: qsTranslate(\"RESP\",\"Regex\")\n                    onCheckedChanged: {\n                        searchToolbar.lastSearchResultPosition = -1;\n                        noResults.visible = false;\n                    }\n                }\n\n                BetterLabel {\n                    id: noResults\n                    objectName: \"rdm_value_editor_search_no_results\"\n                    visible: false\n                }\n\n                Item {\n                    Layout.fillWidth: true\n                }\n\n                ImageButton {\n                    objectName: \"rdm_value_editor_search_clear_btn\"\n                    Layout.preferredWidth: 20\n                    Layout.preferredHeight: 20\n\n                    imgSource: PlatformUtils.getThemeIcon(\"clear.svg\")\n                    onClicked: {\n                        searchToolbar.visible = false;\n                        searchToolbar.lastSearchResultPosition = -1;\n                        noResults.visible = false;\n                        // TODO: clear results & selections\n                    }\n\n                }\n            }\n        }\n\n        Rectangle {\n            id: texteditorWrapper\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            Layout.preferredHeight: 100\n\n            color: sysPalette.base\n            border.color: sysPalette.mid\n            border.width: 1\n            clip: true            \n\n            ScrollView {\n                id: valueScrollView\n                anchors.fill: parent\n                anchors.margins: 5\n                visible: textView.format !== \"image\"\n\n                ScrollBar.vertical.policy: ScrollBar.AlwaysOn\n                ScrollBar.vertical.minimumSize: 0.05\n\n                enabled: !(qmlUtils.isBinaryString(root.value) && qmlUtils.binaryStringLength(root.value) > appSettings.valueSizeLimit)\n\n                ListView {\n                    id: textView\n                    anchors.fill: parent\n                    cacheBuffer: 4\n                    highlightMoveDuration: 0                    \n\n                    Keys.onPressed: {\n                       if (event.matches(StandardKey.Find)) {\n                           searchField.forceActiveFocus()\n                           searchToolbar.visible = true;\n                       } else if (event.matches(StandardKey.SaveAs)) {\n                           saveAsBtn.saveToFile()\n                       } else if (event.matches(StandardKey.Save)) {\n                           saveBtn.saveChanges()\n                       }\n                    }\n\n                    property int textFormat: TextEdit.PlainText\n                    property bool readOnly: false\n                    property string format\n\n                    delegate:\n                        Rectangle {\n                            color: \"transparent\"\n\n                            width: texteditorWrapper.width\n                            height: textAreaPart.contentHeight < texteditorWrapper.height? texteditorWrapper.height - 5 : textAreaPart.contentHeight\n\n                            function selectSearchResult(from, len, txt) {\n                                textAreaPart.select(from, from + len)\n                                textView.contentY = textView.currentItem.y + textAreaPart.cursorRectangle.y\n                            }\n\n                            NewTextArea {\n                                anchors.fill: parent\n                                id: textAreaPart\n                                objectName: \"rdm_key_multiline_text_field_\" + index\n\n                                enabled: root.enabled\n                                text: qmlUtils.isBinaryString(root.value) && qmlUtils.binaryStringLength(root.value) > appSettings.valueSizeLimit ?\n                                          qmlUtils.printable(value, false, 50000) : value;  // Show first 50KB to fit chunkSize\n\n                                textFormat: textView.textFormat\n                                readOnly: textView.readOnly\n                                highlightJSON: textView.format === \"json\"\n\n                                onTextChanged: {\n                                    root.isEdited = true\n                                    textView.model && textView.model.setTextChunk(index, textAreaPart.text)\n                                }\n\n                                Keys.forwardTo: [textView]\n                            }\n                        }\n                }  \n            }\n\n            Image {\n                id: imageView\n                anchors.fill: parent\n                fillMode: Image.PreserveAspectFit\n                visible: textView.format === \"image\"\n            }\n\n        }\n\n        BetterLabel {\n            id: validationError\n            color: \"red\"\n            visible: false\n        }\n\n    }\n\n    BetterDialog {\n        id: largeValueDialog               \n\n        height: 150\n\n        title: qsTranslate(\"RESP\",\"Binary value is too large to display\")\n        visible: false\n        footer: BetterDialogButtonBox {\n            BetterButton {\n                text: qsTranslate(\"RESP\",\"OK\")\n                onClicked: largeValueDialog.close()\n            }\n        }\n\n        RowLayout {\n            anchors.fill: parent\n\n            Text {                \n                color: sysPalette.text\n                text: qsTranslate(\"RESP\",\"Save value to file\")+ \": \"\n            }\n\n            SaveToFileButton {\n                objectName: \"rdm_save_large_raw_value_to_file_dialog_btn\"\n                raw: true\n            }\n        }\n\n        Rectangle {\n            id: uiBlocker\n            visible: false\n            anchors.fill: parent\n            color: Qt.rgba(0, 0, 0, 0.1)\n    \n            Item {\n                anchors.fill: parent\n                BusyIndicator { anchors.centerIn: parent; running: true }\n            }\n    \n            MouseArea { anchors.fill: parent }\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/ReadOnlySingleItemEditor.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.2\nimport QtQuick.Controls.Styles 1.1\n\nimport \".\"\n\nAbstractEditor {\n    id: root\n    anchors.fill: parent\n\n    property bool active: false    \n    property alias defaultFormatter: textEditor.defaultFormatter\n\n    MultilineEditor {\n        id: textEditor\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n        value: \"\"\n        enabled: false\n        showToolBar: false\n        showSaveBtn: false\n        showFormatters: false\n        showValueSize: false\n        objectName: \"rdm_key_value_field\"\n\n        function validationRule(raw) {\n            return true;\n        }\n    }\n\n    onKeyTypeChanged: {        \n       textEditor.hintFormatter(\"JSON\")\n    }\n\n    function initEmpty() {\n        textEditor.initEmpty()      \n    }\n\n    function getValue(validateVal, callback) {\n        if (!validateVal) {\n            return callback(true, {\"value\": \"\"});\n        }\n\n        return textEditor.validate(function (valid, raw) {\n            return callback(valid, {\"value\": raw});\n        });\n    }\n\n    function setValue(rowValue) {\n        if (!rowValue)\n            return\n\n        active = true\n        textEditor.loadFormattedValue(rowValue['value'])\n    }\n\n    function isEdited() {\n        return textEditor.isEdited\n    }    \n\n    function reset() {\n        textEditor.reset()\n        active = false\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/SingleItemEditor.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.2\nimport QtQuick.Controls.Styles 1.1\n\nimport \".\"\n\nAbstractEditor {\n    id: root\n    anchors.fill: parent\n\n    property bool active: false    \n    property alias defaultFormatter: textEditor.defaultFormatter\n\n    MultilineEditor {\n        id: textEditor\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n        value: \"\"\n        enabled: root.active || root.state !== \"edit\"\n        showToolBar: root.state == \"edit\"\n        showSaveBtn: root.state == \"edit\"\n        showFormatters: true\n        showOnlyRWformatters: root.state == \"add\" || root.state == \"new\"\n        objectName: \"rdm_key_value_field\"\n\n        function validationRule(raw) {\n            if (root.keyType === \"string\") return true;\n            return qmlUtils.binaryStringLength(raw) > 0\n        }\n    }\n\n    onKeyTypeChanged: {\n        if (root.keyType === \"ReJSON\") {\n            textEditor.hintFormatter(\"JSON\")\n        }\n    }\n\n    function initEmpty() {\n        textEditor.initEmpty()      \n    }\n\n    function getValue(validateVal, callback) {\n        if (!validateVal) {\n            return callback(true, {\"value\": \"\"});\n        }\n\n        return textEditor.validate(function (valid, raw) {\n            return callback(valid, {\"value\": raw});\n        });\n    }\n\n    function setValue(rowValue) {\n        if (!rowValue)\n            return\n\n        active = true\n        textEditor.loadFormattedValue(rowValue['value'])\n    }\n\n    function isEdited() {\n        return textEditor.isEdited\n    }    \n\n    function reset() {\n        textEditor.reset()\n        active = false\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/SortedSetItemEditor.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.2\nimport QtQuick.Controls.Styles 1.1\n\nimport \".\"\nimport \"../../common\"\n\nAbstractEditor {\n    id: root\n    anchors.fill: parent\n\n    property bool active: false\n    property alias defaultFormatter: textArea.defaultFormatter\n\n    BetterLabel {\n        Layout.fillWidth: true\n        text: qsTranslate(\"RESP\", \"Score\")\n    }\n\n    BetterTextField {\n        id: scoreText\n\n        Layout.fillWidth: true\n        Layout.minimumHeight: 28\n\n        text: \"\"\n        enabled: root.active || root.state !== \"edit\"\n\n        placeholderText: qsTranslate(\"RESP\",\"Score\")\n        validator: DoubleValidator { locale: \"C\"; } // force point as decimal separator\n        objectName: \"rdm_key_zset_score_field\"\n\n        property bool isEdited: false\n\n        onTextChanged: {\n            scoreText.isEdited = true\n        }\n\n        function setValue(v) {\n            text = Number(v)\n            scoreText.isEdited = false\n        }\n\n        Connections {\n            target: keyTab.keyModel ? keyTab.keyModel : null\n\n            onValueUpdated: scoreText.isEdited = false\n        }\n\n        function reset() {\n            text = \"\"\n        }\n    }\n\n    MultilineEditor {\n        id: textArea\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n        value: \"\"\n        enabled: root.active || root.state !== \"edit\"\n        showToolBar: root.state == \"edit\"\n        showSaveBtn: root.state == \"edit\"\n        showFormatters: root.state == \"edit\"\n        objectName: \"rdm_key_zset_text_field\"\n    }\n\n    function initEmpty() {\n        textArea.initEmpty()\n    }\n\n    function getValue(validateVal, callback) {\n        if (!validateVal) {\n            return callback(true, {\"value\": \"\", \"score\": scoreText.text});\n        }\n\n        return textArea.validate(function (valid, raw) {\n            callback(valid, {\"value\": raw, \"score\": scoreText.text})\n        });\n    }\n\n    function setValue(rowValue) {\n        if (!rowValue)\n            return\n\n        active = true\n        scoreText.setValue(rowValue['score'])\n        textArea.loadFormattedValue(rowValue['value'])\n    }\n\n    function isEdited() {\n        return textArea.isEdited || scoreText.isEdited\n    }\n\n    function reset() {\n        root.active = false\n        scoreText.reset()\n        textArea.reset()\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/StreamItemEditor.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.2\nimport QtQuick.Controls.Styles 1.1\n\nimport \".\"\nimport \"../../common\"\n\nAbstractEditor {\n    id: root\n    anchors.fill: parent    \n\n    property bool active: false\n    property alias defaultFormatter: textArea.defaultFormatter\n\n    BetterLabel {\n        Layout.fillWidth: true\n        text: qsTranslate(\"RESP\",\"ID\")\n    }\n\n    BetterTextField {\n            id: idValue\n\n            Layout.fillWidth: true\n            Layout.minimumHeight: 28\n\n            text: \"\"\n            objectName: \"rdm_key_stream_id_field\"\n            property string valueHash: \"\"\n            enabled: root.active || root.state !== \"edit\"\n            readOnly: root.state == \"edit\"\n\n            function reset() {\n                text = (root.state == \"add\" || root.state == \"new\")? \"*\" : \"\"\n            }\n\n            function isEdited() {\n                return Qt.md5(text) != valueHash\n            }\n\n            function setValue(v) {\n                valueHash = Qt.md5(v)\n                text = v\n            }\n\n            function validate(callback) {\n                return callback(text == \"*\" || text.indexOf(\"-\") !== -1, text);\n            }\n    }\n\n    MultilineEditor {\n        id: textArea\n        Layout.fillWidth: true\n        Layout.fillHeight: true        \n        enabled: root.active || root.state !== \"edit\"\n        showToolBar: root.state == \"edit\"\n        showSaveBtn: root.state == \"edit\"\n        showFormatters: root.state == \"edit\"\n        objectName: \"rdm_key_stream_text_field\"\n\n        fieldLabel: qsTranslate(\"RESP\",\"Value (represented as JSON object)\") + \":\"\n\n        function validationRule(raw) {\n            try {\n                var obj = JSON.parse(raw);\n\n                return typeof obj === \"object\";\n            } catch (e) {\n                console.log(\"Json parsing error:\", e)\n                return false\n            }\n        }\n    }\n\n    function initEmpty() {        \n        textArea.initEmpty()\n        idValue.reset()\n    }\n\n    function getValue(validateVal, callback) {\n        idValue.validate(function (keyTextValid, idVal) {\n            if (!validateVal) {\n                return callback(keyTextValid, {\"value\": \"\", \"id\": idVal});\n            } else {\n                textArea.validate(function (textAreaValid, value) {\n                    return callback(keyTextValid && textAreaValid,\n                                    {\"value\": value, \"id\": idVal});\n                });\n            }\n        });\n    }\n\n    function setValue(rowValue) {\n        if (!rowValue)\n            return       \n\n        active = true\n        idValue.setValue(rowValue['id'])\n        textArea.loadFormattedValue(rowValue['value'])\n        textArea.readOnly = root.state == \"edit\"\n    }\n\n    function isEdited() {\n        return textArea.isEdited || idValue.isEdited()\n    }   \n\n    function reset() {\n        textArea.reset()\n        idValue.reset()\n        active = false\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/UnsupportedDataType.qml",
    "content": "import QtQuick 2.0\nimport QtQuick.Layouts 1.1\nimport QtQuick.Controls 1.2\nimport QtQuick.Controls.Styles 1.1\nimport \"./../../common\"\nimport \".\"\n\nAbstractEditor {\n    id: root\n    anchors.fill: parent\n\n    property bool active: false\n    property string keyType: \"\"\n\n    Item {\n        Layout.fillHeight: true\n        Layout.fillWidth: true\n    }\n\n    BetterLabel {\n        Layout.fillWidth: true\n        wrapMode: Text.WordWrap\n        horizontalAlignment: Text.AlignHCenter\n        text: qsTranslate(\"RESP\",\"Unsupported Redis Data type \") + keyType\n        font.pixelSize: 16\n    }\n\n    Loader {\n        id: betaSupportIsAvailable\n        Layout.fillWidth: true\n        asynchronous: true\n        source: keyType? \"https://resp.app/qml/BetaModuleSupport.qml?app_version=\"\n                + Qt.application.version + \"&platform=\" + Qt.platform.os\n                + \"&module=\" + encodeURIComponent(keyType) + \"&t=\" + Date.now() : \"\"\n    }\n\n    Item {\n        Layout.fillHeight: true\n        Layout.fillWidth: true\n    }\n\n    onKeyTypeChanged: {\n        uiBlocker.visible = false;\n    }\n\n    function initEmpty() {\n    }\n\n    function isEdited() {\n        return false\n    }\n\n    function reset() {\n        active = false\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/editor.js",
    "content": "\nfunction getSupportedKeyTypes() {\n    return [\"string\", \"list\", \"set\", \"zset\", \"hash\", \"stream\"]\n}\n\nfunction getEditorByTypeString(keyType, writeOnly) {\n    if (keyType === \"string\"\n            || keyType === \"hyperloglog\"\n            || keyType === \"list\"\n            || keyType === \"set\"\n            || keyType === \"ReJSON\") {\n        return \"./editors/SingleItemEditor.qml\"    \n    } else if (keyType === \"zset\") {\n        return \"./editors/SortedSetItemEditor.qml\"\n    } else if (keyType === \"hash\") {\n        return \"./editors/HashItemEditor.qml\"\n    } else if (keyType === \"stream\") {\n        return \"./editors/StreamItemEditor.qml\"\n    } else if (keyType === \"bf\" || keyType === \"cf\") {\n        if (writeOnly) {\n            return \"./editors/SingleItemEditor.qml\"\n        } else {\n            return \"./editors/ReadOnlySingleItemEditor.qml\"\n        }\n    } else if (keyType) {\n        return \"./editors/UnsupportedDataType.qml\"\n    }\n}\n\n"
  },
  {
    "path": "src/qml/value-editor/editors/formatters/ValueFormatters.qml",
    "content": "import QtQuick 2.0\nimport QtQml.Models 2.13\nimport \"./hexy.js\" as Hexy\nimport \"../../../common/platformutils.js\" as PlatformUtils\n\nListModel {\n    id: rootModel\n\n    property int _jsonFormatterIndex: 3\n\n    function guessFormatter(val, isBinary)\n    {       \n        if (isBinary) {\n            return 1\n        } else {\n            if (qmlUtils.isJSON(val)) {\n                return _jsonFormatterIndex\n            } else {\n                return 0\n            }\n        }\n\n    }\n\n    function getDefaultFormatter(isBinary)\n    {\n        if (isBinary) {\n            return 1\n        } else {\n            return 0\n        }\n    }\n\n    function getJSONFormatter() {\n        return rootModel.get(_jsonFormatterIndex)\n    }\n\n    property var rwFormatters\n\n    function getFormatterIndex(name) {\n        for (var index=0; index < rootModel.count; ++index) {\n            var formatter = get(index);\n\n            if (formatter['name'] == name) {\n                return index;\n            }\n        }\n\n        return 0;\n    }\n\n    function onEmbeddedFormattersLoaded(result) {\n        for (var indx in result) {\n            var formatterName = result[indx][0];\n            var readOnly = result[indx][1];\n\n            var getFormatted = function (formatterName) {\n                var r = function (raw, callback, context) {\n                    return embeddedFormattersManager.decode(formatterName, raw, function (response) {\n                        return callback(response[0], response[1], response[2], response[3])\n                    })\n                }\n                return r\n            };\n\n            var getRaw = function (formatterName) {\n                var r = function (formatted, callback, context) {\n                    return embeddedFormattersManager.encode(formatterName, formatted, function (response) {\n                        return callback(response[0], response[1])\n                    })\n                }\n                return r\n            };\n\n            var isValid = function (formatterName, context) {\n                var r = function (raw, callback) {\n                    return embeddedFormattersManager.isValid(formatterName, raw, function (response) {\n                        return callback(response[0])\n                    })\n                }\n                return r\n            };\n\n            rootModel.append({'name': formatterName, 'type': \"embedded\",})\n            rootModel.setProperty(rootModel.count - 1, \"getFormatted\", getFormatted(formatterName))\n            rootModel.setProperty(rootModel.count - 1, \"getRaw\", getRaw(formatterName))\n            rootModel.setProperty(rootModel.count - 1, \"isValid\", isValid(formatterName))\n            rootModel.setProperty(rootModel.count - 1, \"readOnly\", readOnly)\n            rootModel.setProperty(rootModel.count - 1, \"keyTypes\", \"*\")\n\n        }\n\n        console.log(\"Embedded formatters:\", result);\n    }\n\n    function loadEmbeddedFormatters()\n    {\n        embeddedFormattersManager.loadFormattersModule(function (result) {\n            console.log(\"Is Embedded formatters module loaded:\", result)\n\n            if (!result) {\n                return;\n            }\n\n            embeddedFormattersManager.loadFormatters(function (result) {\n                rootModel.onEmbeddedFormattersLoaded(result);\n            });\n        })\n    }\n\n\n    function loadExternalFormatters() {\n        var nativeFormatters = formattersManager.getPlainList();\n\n        for (var index in nativeFormatters) {\n            var formatter = nativeFormatters[index];\n\n            var formatterName = formatter[\"name\"];\n            var formatterId = formatter[\"id\"];\n            var readOnly = formatter[\"readOnly\"];\n\n            var getFormatted = function (formatterId) {\n                var r = function (raw, callback, context) {\n                    return formattersManager.decode(formatterId, raw, context, callback)\n                }\n                return r\n            };\n\n            var getRaw = function (formatterId) {\n                var r = function (formatted, callback, context) {\n                    return formattersManager.encode(formatterId, formatted, context, callback)\n                }\n                return r\n            };\n\n            var isValid = function (formatterId, context) {\n                var r = function (raw, callback) {\n                    return formattersManager.isValid(formatterId, raw, context, callback)\n                }\n                return r\n            };\n\n            rootModel.append({'name': formatterName, 'type': \"external\"})\n            rootModel.setProperty(rootModel.count - 1, \"getFormatted\", getFormatted(formatterId))\n            rootModel.setProperty(rootModel.count - 1, \"getRaw\", getRaw(formatterId))\n            rootModel.setProperty(rootModel.count - 1, \"isValid\", isValid(formatterId))\n            rootModel.setProperty(rootModel.count - 1, \"readOnly\", readOnly)\n        }\n    }\n\n    function updateRWFormatters() {\n        var result = [];\n\n        for (var index=0; index < rootModel.count; ++index) {\n            var formatter = get(index);\n\n            if (formatter['readOnly'] == false) {\n                result.push(formatter);\n            }\n        }\n\n        rwFormatters = result;\n    }\n\n    ListElement {\n        property string name: \"Plain Text\"\n\n        property string type: \"buildin\"\n\n        property string readOnly: false\n\n        property string keyTypes: \"*\"\n\n        property var getFormatted: function (raw, callback) {\n            return callback(\"\", raw, false, \"plain\")\n        }\n\n        property var isValid: function (raw, callback) {\n            return callback(true)\n        }\n\n        property var getRaw: function (formatted, callback) {\n            return callback(\"\", formatted)\n        }\n    }\n\n    ListElement {\n        property string name: \"HEX\"\n\n        property string type: \"buildin\"\n\n        property string readOnly: true\n\n        property string keyTypes: \"*\"\n\n        property var getFormatted: function (raw, callback, context) {\n            return callback(\"\", qmlUtils.printable(raw), false, \"plain\")\n        }\n\n        property var isValid: function (raw, callback, context) {\n            return callback(true)\n        }\n\n        property var getRaw: function (formatted, callback, context) {\n            return callback(\"\", qmlUtils.printableToValue(formatted))\n        }\n    }\n\n    ListElement {\n        property string name: \"HEX TABLE\"\n\n        property string type: \"buildin\"\n\n        property string readOnly: true\n\n        property string keyTypes: \"*\"\n\n        property var isValid: function (raw, callback, context) {\n            return callback(true)\n        }\n\n        property var getFormatted: function (raw, callback, context) {\n            return callback(\"\", Hexy.hexy(\n                                qmlUtils.valueToBinary(raw),\n                                {'html': true, 'font': appSettings.valueEditorFont}),\n                            true, \"html\")\n        }\n    }\n\n    ListElement {\n        property string name: \"JSON\"\n\n        property string type: \"buildin\"\n\n        property string readOnly: false\n\n        property string keyTypes: \"*\"\n\n        property var getFormatted: function (raw, callback, context) {\n            return callback(\"\", qmlUtils.prettyPrintJSON(raw), false, \"json\")\n        }\n\n        property var isValid: function (raw, callback, context) {\n            return callback(qmlUtils.isJSON(raw))\n        }\n\n        property var getRaw: function (formatted, callback, context) {\n            var minified = qmlUtils.minifyJSON(formatted);\n\n            if (!minified) {\n                return callback(qsTranslate(\"RESP\", \"Error\") + \": Cannot minify JSON string\")\n            } else {\n                return callback(\"\", minified)\n            }\n        }\n    }\n\n    ListElement {\n        property string name: \"BASE64 to Text\"\n\n        property string type: \"buildin\"\n\n        property string readOnly: true\n\n        property string keyTypes: \"*\"\n\n        property var getFormatted: function (raw, callback, context) {\n            return callback(\"\", Qt.atob(raw), false, \"plain\")\n        }\n\n        property var isValid: function (raw, callback, context) {\n            return callback(true)\n        }\n\n        property var getRaw: function (formatted, callback, context) {\n            return callback(\"\", Qt.btoa(formatted))\n        }\n    }\n\n    ListElement {\n        property string name: \"BASE64 to JSON\"\n\n        property string type: \"buildin\"\n\n        property string readOnly: true\n\n        property string keyTypes: \"*\"\n\n        property var getFormatted: function (raw, callback, context) {\n            return callback(\"\", Qt.atob(raw), false, \"json\")\n        }\n\n        property var isValid: function (raw, callback, context) {\n            return callback(true)\n        }\n\n        property var getRaw: function (formatted, callback, context) {\n            return callback(\"\", Qt.btoa(formatted))\n        }\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/editors/formatters/hexy.js",
    "content": "// BASED ON: = hexy.js -- utility to create hex dumps\n// http://github.com/a2800276/hexy.js\n\nvar hexy = function (buffer, config) {\n    var h = new Hexy(buffer, config)\n    return h.toString()\n}\nvar Hexy = function (buffer, config) {\n    var self = {}    \n    config = config || {}\n    self.buffer = buffer // magic string conversion here?\n    self.width = config.width || 16\n    self.numbering = config.numbering == \"none\" ? \"none\" : \"hex_bytes\"\n    switch (config.format) {\n    case \"none\":\n    case \"twos\":\n        self.format = config.format\n        break\n    default:\n        self.format = \"fours\"\n    }\n    self.caps = config.caps == \"upper\" ? \"upper\" : \"lower\"\n    self.annotate = config.annotate == \"none\" ? \"none\" : \"ascii\"\n    self.prefix = config.prefix || \"\"\n    self.indent = config.indent || 0\n    self.html = config.html || false\n    self.offset = config.offset || 0\n    self.length = config.length || -1\n    self.display_offset = config.display_offset || 0\n    self.font = config.font || \"monospace\"\n    if (self.offset) {\n        if (self.offset < self.buffer.length) {\n            self.buffer = self.buffer.slice(self.offset)\n        }\n    }\n    if (self.length !== -1) {\n        if (self.length <= self.buffer.length) {\n            self.buffer = self.buffer.slice(0,self.length)\n        }\n    }\n    for (var i = 0; i!=self.indent; ++i) {\n        self.prefix = \" \"+self.prefix\n    }\n\n    this.toString = function () {\n        var str = \"<style> * { font-family: '\"+ self.font + \"'}</style>\"\n        if (self.html) { str += \"<table border='1'>\\n\"}\n        //split up into line of max `self.width`\n        var line_arr = lines()\n        //lines().forEach(function(hex_raw, i)\n        for (var i = 0; i!= line_arr.length; ++i) {\n            var hex_raw = line_arr[i],\n                    hex = hex_raw[0],\n                    raw = hex_raw[1]\n            //insert spaces every `self.format.twos` or fours\n            var howMany = hex.length\n            if (self.format === \"fours\") {\n                howMany = 4\n            } else if (self.format === \"twos\") {\n                howMany = 2\n            }\n            var hex_formatted = \"\"\n            for (var j =0; j< hex.length; j+=howMany) {\n                var s = hex.substr(j, howMany)\n                hex_formatted += s + \" \"\n            }\n            var addr = (i*self.width)+self.offset+self.display_offset;\n            if (self.html) {\n                var odd = i%2 == 0 ? \" even\" : \" odd\"\n                str += \"<tr class='\"+pad(addr, 8)+odd+\"'>\"\n            }\n            str += self.prefix\n            str += \"<td class='indexes'>\"\n            if (self.numbering === \"hex_bytes\") {\n                str += pad(addr, 8) // padding...\n                str += \": \"\n            }\n            str += \"</td><td>\"\n            var padlen = 0\n            switch(self.format) {\n            case \"eights\":\n                padlen = self.width*2 + Math.floor(self.width/4)\n                break\n            case \"fours\":\n                padlen = self.width*2 + Math.floor(self.width/2)\n                break\n            case \"twos\":\n                padlen = self.width*3 + 2\n                break\n            default:\n                padlen = self.width * 2 + 1\n            }\n            str += rpad(hex_formatted, padlen)\n            str += \"</td><td>\"\n            if (self.annotate === \"ascii\") {\n                str+=\" \"\n                str += escape(raw.replace(/[\\000-\\040\\177-\\377]/g, \".\"))\n            }\n            if (self.html) {\n                str += \"</td></tr>\\n\"\n            } else {\n                str += \"\\n\"\n            }\n        }\n        if (self.html) { str += \"</table>\\n\"}\n        return str\n    }\n    var lines = function() {\n        var hex_raw = []\n        for (var i = 0; i<self.buffer.length ; i+=self.width) {\n            var begin = i,\n                    end = i+self.width >= self.buffer.length ? self.buffer.length : i+self.width,\n                                                               slice = self.buffer.slice(begin, end),\n                                                               hex = self.caps === \"upper\" ? hexu(slice) : hexl(slice),\n                                                                                             raw = String.fromCharCode.apply(null, slice)\n            hex_raw.push([hex,raw])\n        }\n        return hex_raw\n    }\n    var hexl = function (buffer) {\n        var str = \"\"\n        for (var i=0; i!=buffer.length; ++i) {\n            if (buffer.constructor == String) {\n                str += pad(buffer.charCodeAt(i), 2)\n            } else {\n                str += pad(buffer[i], 2)\n            }\n        }\n        return str\n    }\n    var hexu = function (buffer) {\n        return hexl(buffer).toUpperCase()\n    }\n    var pad = function(b, len) {\n        var s = b.toString(16)\n        while (s.length < len) {\n            s = \"0\" + s\n        }\n        return s\n    }\n    var rpad = function(s, len) {\n        for (var n = len - s.length; n>0; --n) {\n            if (self.html) {\n                s += \"&nbsp;\"\n            } else {\n                s += \" \"\n            }\n        }\n        return s\n    }\n    var escape = function (str) {\n        str = str.split(\"&\").join(\"&amp;\")\n        str = str.split(\"<\").join(\"&lt;\")\n        str = str.split(\">\").join(\"&gt;\")\n        return str\n    }\n}\n"
  },
  {
    "path": "src/qml/value-editor/filters/ListFilters.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Layouts 1.1\nimport \"./../../common\"\n\nRowLayout {\n\n    BetterLabel {\n        text: qsTranslate(\"RESP\", \"Order of elements:\")\n    }\n\n    BetterComboBox {\n        id: filterDirection\n\n        enabled: !keyTab.keyModel.singlePageMode\n\n        ListModel {\n            id: filterDirectionModel\n\n            Component.onCompleted: {\n                filterDirectionModel.append({ value: \"default\", text: qsTranslate(\"RESP\", \"Default\") })\n                filterDirectionModel.append({ value: \"reverse\", text: qsTranslate(\"RESP\", \"Reverse\") })\n                filterDirection.currentIndex = 0\n            }\n        }\n\n        textRole: \"text\"\n\n        model: filterDirectionModel\n        onCurrentIndexChanged: {\n            var direction = filterDirectionModel.get(currentIndex)[\"value\"];\n            keyModel.setFilter(\"order\", direction);\n            reloadValue();\n        }\n    }\n\n    Item { Layout.fillWidth: true }\n}\n"
  },
  {
    "path": "src/qml/value-editor/filters/StreamFilters.qml",
    "content": "import QtQuick 2.13\nimport QtQuick.Controls 2.13\nimport QtQuick.Layouts 1.1\nimport QtQuick.Window 2.2\nimport \"./../../common\"\nimport \"../../common/platformutils.js\" as PlatformUtils\n\nRowLayout {\n    id: streamFilter\n    objectName: \"rdm_stream_filter\"\n\n    property bool enabled: streamRangeSlider.from < streamRangeSlider.to\n    property string dateTimeFormat: PlatformUtils.dateTimeFormat\n    property string inputMask: \"9999-99-99 99:99:99.999\"\n\n    function setStreamFilter() {\n        var start = Date.fromLocaleString(locale, streamRangeStartField.text, streamFilter.dateTimeFormat).getTime()\n        var end = Date.fromLocaleString(locale, streamRangeEndField.text, streamFilter.dateTimeFormat).getTime()\n        if (start < end) {\n            keyTab.keyModel.setFilter(\"start\", start)\n            keyTab.keyModel.setFilter(\"end\", end)\n\n            reloadValue()\n\n            streamRangeStartField.isEdited = false;\n            streamRangeEndField.isEdited = false;\n        } else {\n            notification.showError(qsTranslate(\"RESP\",\"Start date should be less than End date\"))\n        }\n    }\n\n    RegExpValidator {\n        id: dateTimeValidator\n        regExp: /(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2})\\:(\\d{2})\\:(\\d{2}).(\\d{3})/\n    }\n\n    BetterTextField {\n        id: streamRangeStartField\n        objectName: \"rdm_stream_filter_start_field\"\n\n        property bool isEdited: false\n\n        implicitWidth: 180\n        font.pixelSize: 14\n        color: enabled ? sysPalette.text : disabledSysPalette.text\n\n        enabled: streamFilter.enabled\n\n        text: new Date(streamRangeSlider.first.value).toLocaleString(locale, streamFilter.dateTimeFormat)\n        inputMask: streamFilter.inputMask\n        validator: dateTimeValidator\n\n        BetterToolTip {\n            title: streamRangeSlider.first.value\n            visible: title && (streamRangeSlider.first.pressed || streamRangeSlider.first.hovered)\n        }\n\n        onTextEdited: {\n            isEdited = true\n        }\n\n        onAccepted: {\n            streamFilter.setStreamFilter()\n        }\n    }\n\n    RangeSlider {\n        id: streamRangeSlider\n        objectName: \"rdm_stream_filter_range_slider\"\n\n        implicitWidth: 100\n\n        Layout.fillWidth: true\n        palette.midlight: sysPalette.button\n        palette.dark: enabled ? sysPalette.highlight : disabledSysPalette.highlight\n        padding: 0\n\n        enabled: streamFilter.enabled\n\n        stepSize: 1.0\n        snapMode: Slider.SnapAlways\n\n        first.handle.implicitWidth: PlatformUtils.isOSXRetina(Screen) ? 15 : 20\n        first.handle.implicitHeight: PlatformUtils.isOSXRetina(Screen) ? 15 : 20\n\n        second.handle.implicitWidth: PlatformUtils.isOSXRetina(Screen) ? 15 : 20\n        second.handle.implicitHeight: PlatformUtils.isOSXRetina(Screen) ? 15 : 20\n\n        first.onPressedChanged: {\n            if (!first.pressed) {\n                streamRangeStartField.isEdited = true\n            }\n        }\n\n        second.onPressedChanged: {\n            if (!second.pressed) {\n                streamRangeEndField.isEdited = true\n            }\n        }\n    }\n\n    BetterTextField {\n        id: streamRangeEndField\n        objectName: \"rdm_stream_filter_end_field\"\n\n        property bool isEdited: false\n\n        implicitWidth: 180\n        font.pixelSize: 14\n        color: enabled ? sysPalette.text : disabledSysPalette.text\n\n        enabled: streamFilter.enabled\n\n        text: new Date(streamRangeSlider.second.value).toLocaleString(locale, streamFilter.dateTimeFormat)\n        inputMask: streamFilter.inputMask\n        validator: dateTimeValidator\n\n        BetterToolTip {\n            title: streamRangeSlider.second.value\n            visible: title && (streamRangeSlider.second.pressed || streamRangeSlider.second.hovered)\n        }\n\n        onTextEdited: {\n            isEdited = true\n        }\n\n        onAccepted: {\n            streamFilter.setStreamFilter()\n        }\n    }\n\n    BetterButton {\n        objectName: \"rdm_stream_filter_apply_btn\"\n        implicitWidth: 30\n        iconSource: PlatformUtils.getThemeIcon(\"filter.svg\")\n        tooltip: qsTranslate(\"RESP\",\"Apply filter\")\n        enabled: (streamRangeStartField.isEdited || streamRangeEndField.isEdited) && streamFilter.enabled\n\n        onClicked: {\n            streamFilter.setStreamFilter()\n        }\n    }\n\n    Connections {\n        target: keyModel ? keyModel : null\n\n        function onRowsLoaded() {\n\n            var firstEntry = String(keyModel.filter(\"first-entry\")).slice(0, -2)\n            var lastEntry = String(keyModel.filter(\"last-entry\")).slice(0, -2)\n\n            var start = keyModel.filter(\"start\") ? keyModel.filter(\"start\") : firstEntry\n            var end = keyModel.filter(\"end\") ? keyModel.filter(\"end\") : lastEntry\n\n            console.log(\"STREAM filter start end:\", start, end)\n\n            streamRangeSlider.from = Number(firstEntry)\n            streamRangeSlider.to = Number(lastEntry)\n\n            streamRangeSlider.first.value = Number(start)\n            streamRangeSlider.second.value = Number(end)\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/resources/Info.plist.sample",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en-US</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>RESP</string>\n\t<key>CFBundleExecutable</key>\n\t<string>RESP</string>\n\t<key>CFBundleIconFile</key>\n\t<string>logo.icns</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.redisdesktop.rdm</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n        <key>CFBundleName</key>\n\t<string>RESP</string>\n        <key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>0.0.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>0.0.0</string>\n        <key>NSHumanReadableCopyright</key>\n        <string>© 2013-2019, Igor Malinovskiy.</string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n\t<key>NSHighResolutionCapable</key>\n\t<true/>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>NSSupportsAutomaticGraphicsSwitching</key>\n\t<true/>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.developer-tools</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array><string>MacOSX</string></array>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.14.0</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "src/resources/commands.json",
    "content": "[{\"cmd\": \"ACL\", \"summary\": \"A container for Access List Control commands \", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL CAT\", \"summary\": \"List the ACL categories or the commands inside a category\", \"arguments\": \"[categoryname]\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL DELUSER\", \"summary\": \"Remove the specified ACL users and the associated rules\", \"arguments\": \"username\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL GENPASS\", \"summary\": \"Generate a pseudorandom secure password to use for ACL users\", \"arguments\": \"[bits]\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL GETUSER\", \"summary\": \"Get the rules for a specific ACL user\", \"arguments\": \"username\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL LIST\", \"summary\": \"List the current ACL rules in ACL config file format\", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL LOAD\", \"summary\": \"Reload the ACLs from the configured ACL file\", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL LOG\", \"summary\": \"List latest events denied because of ACLs in place\", \"arguments\": \"[operation]\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL SAVE\", \"summary\": \"Save the current ACL rules in the configured ACL file\", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL SETUSER\", \"summary\": \"Modify or create the rules for a specific ACL user\", \"arguments\": \"username [rule]\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL USERS\", \"summary\": \"List the username of all the configured ACL rules\", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"ACL WHOAMI\", \"summary\": \"Return the name of the user associated to the current connection\", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"APPEND\", \"summary\": \"Append a value to a key\", \"arguments\": \"key value\", \"since\": \"2.0.0\"}, {\"cmd\": \"ASKING\", \"summary\": \"Sent by cluster clients after an -ASK redirect\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"AUTH\", \"summary\": \"Authenticate to the server\", \"arguments\": \"[username] password\", \"since\": \"1.0.0\"}, {\"cmd\": \"BGREWRITEAOF\", \"summary\": \"Asynchronously rewrite the append-only file\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"BGSAVE\", \"summary\": \"Asynchronously save the dataset to disk\", \"arguments\": \"[schedule]\", \"since\": \"1.0.0\"}, {\"cmd\": \"BITCOUNT\", \"summary\": \"Count set bits in a string\", \"arguments\": \"key [index]\", \"since\": \"2.6.0\"}, {\"cmd\": \"BITFIELD\", \"summary\": \"Perform arbitrary bitfield integer operations on strings\", \"arguments\": \"key [encoding_offset] [encoding_offset_value] [encoding_offset_increment] [wrap_sat_fail]\", \"since\": \"3.2.0\"}, {\"cmd\": \"BITFIELD_RO\", \"summary\": \"Perform arbitrary bitfield integer operations on strings. Read-only variant of BITFIELD\", \"arguments\": \"key encoding_offset\", \"since\": \"6.2.0\"}, {\"cmd\": \"BITOP\", \"summary\": \"Perform bitwise operations between strings\", \"arguments\": \"operation destkey key\", \"since\": \"2.6.0\"}, {\"cmd\": \"BITPOS\", \"summary\": \"Find first bit set or clear in a string\", \"arguments\": \"key bit [index]\", \"since\": \"2.8.7\"}, {\"cmd\": \"BLMOVE\", \"summary\": \"Pop an element from a list, push it to another list and return it; or block until one is available\", \"arguments\": \"source destination wherefrom whereto timeout\", \"since\": \"6.2.0\"}, {\"cmd\": \"BLMPOP\", \"summary\": \"Pop elements from a list, or block until one is available\", \"arguments\": \"timeout numkeys key where [count]\", \"since\": \"7.0.0\"}, {\"cmd\": \"BLPOP\", \"summary\": \"Remove and get the first element in a list, or block until one is available\", \"arguments\": \"key timeout\", \"since\": \"2.0.0\"}, {\"cmd\": \"BRPOP\", \"summary\": \"Remove and get the last element in a list, or block until one is available\", \"arguments\": \"key timeout\", \"since\": \"2.0.0\"}, {\"cmd\": \"BRPOPLPUSH\", \"summary\": \"Pop an element from a list, push it to another list and return it; or block until one is available\", \"arguments\": \"source destination timeout\", \"since\": \"2.2.0\"}, {\"cmd\": \"BZMPOP\", \"summary\": \"Remove and return members with scores in a sorted set or block until one is available\", \"arguments\": \"timeout numkeys key where [count]\", \"since\": \"7.0.0\"}, {\"cmd\": \"BZPOPMAX\", \"summary\": \"Remove and return the member with the highest score from one or more sorted sets, or block until one is available\", \"arguments\": \"key timeout\", \"since\": \"5.0.0\"}, {\"cmd\": \"BZPOPMIN\", \"summary\": \"Remove and return the member with the lowest score from one or more sorted sets, or block until one is available\", \"arguments\": \"key timeout\", \"since\": \"5.0.0\"}, {\"cmd\": \"CLIENT\", \"summary\": \"A container for client connection commands\", \"arguments\": \"\", \"since\": \"2.4.0\"}, {\"cmd\": \"CLIENT CACHING\", \"summary\": \"Instruct the server about tracking or not keys in the next request\", \"arguments\": \"mode\", \"since\": \"6.0.0\"}, {\"cmd\": \"CLIENT GETNAME\", \"summary\": \"Get the current connection name\", \"arguments\": \"\", \"since\": \"2.6.9\"}, {\"cmd\": \"CLIENT GETREDIR\", \"summary\": \"Get tracking notifications redirection client ID if any\", \"arguments\": \"\", \"since\": \"6.0.0\"}, {\"cmd\": \"CLIENT HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"CLIENT ID\", \"summary\": \"Returns the client ID for the current connection\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"CLIENT INFO\", \"summary\": \"Returns information about the current client connection.\", \"arguments\": \"\", \"since\": \"6.2.0\"}, {\"cmd\": \"CLIENT KILL\", \"summary\": \"Kill the connection of a client\", \"arguments\": \"[ip:port] [client-id] [normal_master_slave_pubsub] [username] [ip:port] [ip:port] [yes/no]\", \"since\": \"2.4.0\"}, {\"cmd\": \"CLIENT LIST\", \"summary\": \"Get the list of client connections\", \"arguments\": \"[normal_master_replica_pubsub] [id]\", \"since\": \"2.4.0\"}, {\"cmd\": \"CLIENT NO-EVICT\", \"summary\": \"Set client eviction mode for the current connection\", \"arguments\": \"enabled\", \"since\": \"7.0.0\"}, {\"cmd\": \"CLIENT PAUSE\", \"summary\": \"Stop processing commands from clients for some time\", \"arguments\": \"timeout [mode]\", \"since\": \"2.9.50\"}, {\"cmd\": \"CLIENT REPLY\", \"summary\": \"Instruct the server whether to reply to commands\", \"arguments\": \"on_off_skip\", \"since\": \"3.2.0\"}, {\"cmd\": \"CLIENT SETNAME\", \"summary\": \"Set the current connection name\", \"arguments\": \"connection-name\", \"since\": \"2.6.9\"}, {\"cmd\": \"CLIENT TRACKING\", \"summary\": \"Enable or disable server assisted client side caching support\", \"arguments\": \"status [client-id] [prefix] [bcast] [optin] [optout] [noloop]\", \"since\": \"6.0.0\"}, {\"cmd\": \"CLIENT TRACKINGINFO\", \"summary\": \"Return information about server assisted client side caching for the current connection\", \"arguments\": \"\", \"since\": \"6.2.0\"}, {\"cmd\": \"CLIENT UNBLOCK\", \"summary\": \"Unblock a client blocked in a blocking command from a different connection\", \"arguments\": \"client-id [timeout_error]\", \"since\": \"5.0.0\"}, {\"cmd\": \"CLIENT UNPAUSE\", \"summary\": \"Resume processing of clients that were paused\", \"arguments\": \"\", \"since\": \"6.2.0\"}, {\"cmd\": \"CLUSTER\", \"summary\": \"A container for cluster commands\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER ADDSLOTS\", \"summary\": \"Assign new hash slots to receiving node\", \"arguments\": \"slot\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER ADDSLOTSRANGE\", \"summary\": \"Assign new hash slots to receiving node\", \"arguments\": \"start-slot_end-slot\", \"since\": \"7.0.0\"}, {\"cmd\": \"CLUSTER BUMPEPOCH\", \"summary\": \"Advance the cluster config epoch\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER COUNT-FAILURE-REPORTS\", \"summary\": \"Return the number of failure reports active for a given node\", \"arguments\": \"node-id\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER COUNTKEYSINSLOT\", \"summary\": \"Return the number of local keys in the specified hash slot\", \"arguments\": \"slot\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER DELSLOTS\", \"summary\": \"Set hash slots as unbound in receiving node\", \"arguments\": \"slot\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER DELSLOTSRANGE\", \"summary\": \"Set hash slots as unbound in receiving node\", \"arguments\": \"start-slot_end-slot\", \"since\": \"7.0.0\"}, {\"cmd\": \"CLUSTER FAILOVER\", \"summary\": \"Forces a replica to perform a manual failover of its master.\", \"arguments\": \"[options]\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER FLUSHSLOTS\", \"summary\": \"Delete a node's own slots information\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER FORGET\", \"summary\": \"Remove a node from the nodes table\", \"arguments\": \"node-id\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER GETKEYSINSLOT\", \"summary\": \"Return local key names in the specified hash slot\", \"arguments\": \"slot count\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"CLUSTER INFO\", \"summary\": \"Provides info about Redis Cluster node state\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER KEYSLOT\", \"summary\": \"Returns the hash slot of the specified key\", \"arguments\": \"key\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER LINKS\", \"summary\": \"Returns a list of all TCP links to and from peer nodes in cluster\", \"arguments\": \"\", \"since\": \"7.0.0\"}, {\"cmd\": \"CLUSTER MEET\", \"summary\": \"Force a node cluster to handshake with another node\", \"arguments\": \"ip port\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER MYID\", \"summary\": \"Return the node id\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER NODES\", \"summary\": \"Get Cluster config for the node\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER REPLICAS\", \"summary\": \"List replica nodes of the specified master node\", \"arguments\": \"node-id\", \"since\": \"5.0.0\"}, {\"cmd\": \"CLUSTER REPLICATE\", \"summary\": \"Reconfigure a node as a replica of the specified master node\", \"arguments\": \"node-id\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER RESET\", \"summary\": \"Reset a Redis Cluster node\", \"arguments\": \"[hard_soft]\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER SAVECONFIG\", \"summary\": \"Forces the node to save cluster state on disk\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER SET-CONFIG-EPOCH\", \"summary\": \"Set the configuration epoch in a new node\", \"arguments\": \"config-epoch\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER SETSLOT\", \"summary\": \"Bind a hash slot to a specific node\", \"arguments\": \"slot subcommand\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER SLAVES\", \"summary\": \"List replica nodes of the specified master node\", \"arguments\": \"node-id\", \"since\": \"3.0.0\"}, {\"cmd\": \"CLUSTER SLOTS\", \"summary\": \"Get array of Cluster slot to node mappings\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"COMMAND\", \"summary\": \"Get array of Redis command details\", \"arguments\": \"\", \"since\": \"2.8.13\"}, {\"cmd\": \"COMMAND COUNT\", \"summary\": \"Get total number of Redis commands\", \"arguments\": \"\", \"since\": \"2.8.13\"}, {\"cmd\": \"COMMAND GETKEYS\", \"summary\": \"Extract keys given a full Redis command\", \"arguments\": \"\", \"since\": \"2.8.13\"}, {\"cmd\": \"COMMAND HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"COMMAND INFO\", \"summary\": \"Get array of specific Redis command details\", \"arguments\": \"command-name\", \"since\": \"2.8.13\"}, {\"cmd\": \"COMMAND LIST\", \"summary\": \"Get an array of Redis command names\", \"arguments\": \"[filterby]\", \"since\": \"7.0.0\"}, {\"cmd\": \"CONFIG\", \"summary\": \"A container for server configuration commands\", \"arguments\": \"\", \"since\": \"2.0.0\"}, {\"cmd\": \"CONFIG GET\", \"summary\": \"Get the values of configuration parameters\", \"arguments\": \"parameter\", \"since\": \"2.0.0\"}, {\"cmd\": \"CONFIG HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"CONFIG RESETSTAT\", \"summary\": \"Reset the stats returned by INFO\", \"arguments\": \"\", \"since\": \"2.0.0\"}, {\"cmd\": \"CONFIG REWRITE\", \"summary\": \"Rewrite the configuration file with the in memory configuration\", \"arguments\": \"\", \"since\": \"2.8.0\"}, {\"cmd\": \"CONFIG SET\", \"summary\": \"Set configuration parameters to the given values\", \"arguments\": \"parameter_value\", \"since\": \"2.0.0\"}, {\"cmd\": \"COPY\", \"summary\": \"Copy a key\", \"arguments\": \"source destination [destination-db] [replace]\", \"since\": \"6.2.0\"}, {\"cmd\": \"DBSIZE\", \"summary\": \"Return the number of keys in the selected database\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"DEBUG\", \"summary\": \"A container for debugging commands\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"DECR\", \"summary\": \"Decrement the integer value of a key by one\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"DECRBY\", \"summary\": \"Decrement the integer value of a key by the given number\", \"arguments\": \"key decrement\", \"since\": \"1.0.0\"}, {\"cmd\": \"DEL\", \"summary\": \"Delete a key\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"DISCARD\", \"summary\": \"Discard all commands issued after MULTI\", \"arguments\": \"\", \"since\": \"2.0.0\"}, {\"cmd\": \"DUMP\", \"summary\": \"Return a serialized version of the value stored at the specified key.\", \"arguments\": \"key\", \"since\": \"2.6.0\"}, {\"cmd\": \"ECHO\", \"summary\": \"Echo the given string\", \"arguments\": \"message\", \"since\": \"1.0.0\"}, {\"cmd\": \"EVAL\", \"summary\": \"Execute a Lua script server side\", \"arguments\": \"script numkeys [key] [arg]\", \"since\": \"2.6.0\"}, {\"cmd\": \"EVALSHA\", \"summary\": \"Execute a Lua script server side\", \"arguments\": \"sha1 numkeys [key] [arg]\", \"since\": \"2.6.0\"}, {\"cmd\": \"EVALSHA_RO\", \"summary\": \"Execute a read-only Lua script server side\", \"arguments\": \"sha1 numkeys key arg\", \"since\": \"7.0.0\"}, {\"cmd\": \"EVAL_RO\", \"summary\": \"Execute a read-only Lua script server side\", \"arguments\": \"script numkeys key arg\", \"since\": \"7.0.0\"}, {\"cmd\": \"EXEC\", \"summary\": \"Execute all commands issued after MULTI\", \"arguments\": \"\", \"since\": \"1.2.0\"}, {\"cmd\": \"EXISTS\", \"summary\": \"Determine if a key exists\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"EXPIRE\", \"summary\": \"Set a key's time to live in seconds\", \"arguments\": \"key seconds [condition]\", \"since\": \"1.0.0\"}, {\"cmd\": \"EXPIREAT\", \"summary\": \"Set the expiration for a key as a UNIX timestamp\", \"arguments\": \"key timestamp [condition]\", \"since\": \"1.2.0\"}, {\"cmd\": \"EXPIRETIME\", \"summary\": \"Get the expiration Unix timestamp for a key\", \"arguments\": \"key\", \"since\": \"7.0.0\"}, {\"cmd\": \"FAILOVER\", \"summary\": \"Start a coordinated failover between this server and one of its replicas.\", \"arguments\": \"[target] [abort] [milliseconds]\", \"since\": \"6.2.0\"}, {\"cmd\": \"FCALL\", \"summary\": \"PATCH__TBD__38__\", \"arguments\": \"function numkeys key arg\", \"since\": \"7.0.0\"}, {\"cmd\": \"FCALL_RO\", \"summary\": \"PATCH__TBD__7__\", \"arguments\": \"function numkeys key arg\", \"since\": \"7.0.0\"}, {\"cmd\": \"FLUSHALL\", \"summary\": \"Remove all keys from all databases\", \"arguments\": \"[async]\", \"since\": \"1.0.0\"}, {\"cmd\": \"FLUSHDB\", \"summary\": \"Remove all keys from the current database\", \"arguments\": \"[async]\", \"since\": \"1.0.0\"}, {\"cmd\": \"FUNCTION\", \"summary\": \"A container for function commands\", \"arguments\": \"\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION CREATE\", \"summary\": \"Create a function with the given arguments (name, code, description)\", \"arguments\": \"engine-name function-name [replace] [function-description] function-code\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION DELETE\", \"summary\": \"Delete a function by name\", \"arguments\": \"function-name\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION DUMP\", \"summary\": \"Dump all functions into a serialized binary payload\", \"arguments\": \"\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION FLUSH\", \"summary\": \"Deleting all functions\", \"arguments\": \"[async]\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION INFO\", \"summary\": \"Return information about a function by function name\", \"arguments\": \"function-name [withcode]\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION KILL\", \"summary\": \"Kill the function currently in execution.\", \"arguments\": \"\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION LIST\", \"summary\": \"List information about all the functions\", \"arguments\": \"\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION RESTORE\", \"summary\": \"Restore all the functions on the given payload\", \"arguments\": \"serialized-value [policy]\", \"since\": \"7.0.0\"}, {\"cmd\": \"FUNCTION STATS\", \"summary\": \"Return information about the function currently running (name, description, duration)\", \"arguments\": \"\", \"since\": \"7.0.0\"}, {\"cmd\": \"GEOADD\", \"summary\": \"Add one or more geospatial items in the geospatial index represented using a sorted set\", \"arguments\": \"key [condition] [change] longitude_latitude_member\", \"since\": \"3.2.0\"}, {\"cmd\": \"GEODIST\", \"summary\": \"Returns the distance between two members of a geospatial index\", \"arguments\": \"key member1 member2 [unit]\", \"since\": \"3.2.0\"}, {\"cmd\": \"GEOHASH\", \"summary\": \"Returns members of a geospatial index as standard geohash strings\", \"arguments\": \"key member\", \"since\": \"3.2.0\"}, {\"cmd\": \"GEOPOS\", \"summary\": \"Returns longitude and latitude of members of a geospatial index\", \"arguments\": \"key member\", \"since\": \"3.2.0\"}, {\"cmd\": \"GEORADIUS\", \"summary\": \"Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point\", \"arguments\": \"key longitude latitude radius unit [withcoord] [withdist] [withhash] [count] [order] [key] [key]\", \"since\": \"3.2.0\"}, {\"cmd\": \"GEORADIUSBYMEMBER\", \"summary\": \"Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member\", \"arguments\": \"key member radius unit [withcoord] [withdist] [withhash] [count] [order] [key] [key]\", \"since\": \"3.2.0\"}, {\"cmd\": \"GEORADIUSBYMEMBER_RO\", \"summary\": \"A read-only variant for GEORADIUSBYMEMBER\", \"arguments\": \"key member radius unit [withcoord] [withdist] [withhash] [count] [order]\", \"since\": \"3.2.10\"}, {\"cmd\": \"GEORADIUS_RO\", \"summary\": \"A read-only variant for GEORADIUS\", \"arguments\": \"key longitude latitude radius unit [withcoord] [withdist] [withhash] [count] [order]\", \"since\": \"3.2.10\"}, {\"cmd\": \"GEOSEARCH\", \"summary\": \"Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle.\", \"arguments\": \"key [member] [longitude_latitude] [circle] [box] [order] [count] [withcoord] [withdist] [withhash]\", \"since\": \"6.2.0\"}, {\"cmd\": \"GEOSEARCHSTORE\", \"summary\": \"Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle, and store the result in another key.\", \"arguments\": \"destination source [member] [longitude_latitude] [circle] [box] [order] [count] [storedist]\", \"since\": \"6.2.0\"}, {\"cmd\": \"GET\", \"summary\": \"Get the value of a key\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"GETBIT\", \"summary\": \"Returns the bit value at offset in the string value stored at key\", \"arguments\": \"key offset\", \"since\": \"2.2.0\"}, {\"cmd\": \"GETDEL\", \"summary\": \"Get the value of a key and delete the key\", \"arguments\": \"key\", \"since\": \"6.2.0\"}, {\"cmd\": \"GETEX\", \"summary\": \"Get the value of a key and optionally set its expiration\", \"arguments\": \"key [expiration]\", \"since\": \"6.2.0\"}, {\"cmd\": \"GETRANGE\", \"summary\": \"Get a substring of the string stored at a key\", \"arguments\": \"key start end\", \"since\": \"2.4.0\"}, {\"cmd\": \"GETSET\", \"summary\": \"Set the string value of a key and return its old value\", \"arguments\": \"key value\", \"since\": \"1.0.0\"}, {\"cmd\": \"HDEL\", \"summary\": \"Delete one or more hash fields\", \"arguments\": \"key field\", \"since\": \"2.0.0\"}, {\"cmd\": \"HELLO\", \"summary\": \"Handshake with Redis\", \"arguments\": \"[arguments]\", \"since\": \"6.0.0\"}, {\"cmd\": \"HEXISTS\", \"summary\": \"Determine if a hash field exists\", \"arguments\": \"key field\", \"since\": \"2.0.0\"}, {\"cmd\": \"HGET\", \"summary\": \"Get the value of a hash field\", \"arguments\": \"key field\", \"since\": \"2.0.0\"}, {\"cmd\": \"HGETALL\", \"summary\": \"Get all the fields and values in a hash\", \"arguments\": \"key\", \"since\": \"2.0.0\"}, {\"cmd\": \"HINCRBY\", \"summary\": \"Increment the integer value of a hash field by the given number\", \"arguments\": \"key field increment\", \"since\": \"2.0.0\"}, {\"cmd\": \"HINCRBYFLOAT\", \"summary\": \"Increment the float value of a hash field by the given amount\", \"arguments\": \"key field increment\", \"since\": \"2.6.0\"}, {\"cmd\": \"HKEYS\", \"summary\": \"Get all the fields in a hash\", \"arguments\": \"key\", \"since\": \"2.0.0\"}, {\"cmd\": \"HLEN\", \"summary\": \"Get the number of fields in a hash\", \"arguments\": \"key\", \"since\": \"2.0.0\"}, {\"cmd\": \"HMGET\", \"summary\": \"Get the values of all the given hash fields\", \"arguments\": \"key field\", \"since\": \"2.0.0\"}, {\"cmd\": \"HMSET\", \"summary\": \"Set multiple hash fields to multiple values\", \"arguments\": \"key field_value\", \"since\": \"2.0.0\"}, {\"cmd\": \"HRANDFIELD\", \"summary\": \"Get one or multiple random fields from a hash\", \"arguments\": \"key [options]\", \"since\": \"6.2.0\"}, {\"cmd\": \"HSCAN\", \"summary\": \"Incrementally iterate hash fields and associated values\", \"arguments\": \"key cursor [pattern] [count]\", \"since\": \"2.8.0\"}, {\"cmd\": \"HSET\", \"summary\": \"Set the string value of a hash field\", \"arguments\": \"key field_value\", \"since\": \"2.0.0\"}, {\"cmd\": \"HSETNX\", \"summary\": \"Set the value of a hash field, only if the field does not exist\", \"arguments\": \"key field value\", \"since\": \"2.0.0\"}, {\"cmd\": \"HSTRLEN\", \"summary\": \"Get the length of the value of a hash field\", \"arguments\": \"key field\", \"since\": \"3.2.0\"}, {\"cmd\": \"HVALS\", \"summary\": \"Get all the values in a hash\", \"arguments\": \"key\", \"since\": \"2.0.0\"}, {\"cmd\": \"INCR\", \"summary\": \"Increment the integer value of a key by one\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"INCRBY\", \"summary\": \"Increment the integer value of a key by the given amount\", \"arguments\": \"key increment\", \"since\": \"1.0.0\"}, {\"cmd\": \"INCRBYFLOAT\", \"summary\": \"Increment the float value of a key by the given amount\", \"arguments\": \"key increment\", \"since\": \"2.6.0\"}, {\"cmd\": \"INFO\", \"summary\": \"Get information and statistics about the server\", \"arguments\": \"[section]\", \"since\": \"1.0.0\"}, {\"cmd\": \"KEYS\", \"summary\": \"Find all keys matching the given pattern\", \"arguments\": \"pattern\", \"since\": \"1.0.0\"}, {\"cmd\": \"LASTSAVE\", \"summary\": \"Get the UNIX time stamp of the last successful save to disk\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"LATENCY\", \"summary\": \"A container for latency diagnostics commands\", \"arguments\": \"\", \"since\": \"2.8.13\"}, {\"cmd\": \"LATENCY DOCTOR\", \"summary\": \"Return a human readable latency analysis report.\", \"arguments\": \"\", \"since\": \"2.8.13\"}, {\"cmd\": \"LATENCY GRAPH\", \"summary\": \"Return a latency graph for the event.\", \"arguments\": \"event\", \"since\": \"2.8.13\"}, {\"cmd\": \"LATENCY HELP\", \"summary\": \"Show helpful text about the different subcommands.\", \"arguments\": \"\", \"since\": \"2.8.13\"}, {\"cmd\": \"LATENCY HISTORY\", \"summary\": \"Return timestamp-latency samples for the event.\", \"arguments\": \"event\", \"since\": \"2.8.13\"}, {\"cmd\": \"LATENCY LATEST\", \"summary\": \"Return the latest latency samples for all events.\", \"arguments\": \"\", \"since\": \"2.8.13\"}, {\"cmd\": \"LATENCY RESET\", \"summary\": \"Reset latency data for one or more events.\", \"arguments\": \"[event]\", \"since\": \"2.8.13\"}, {\"cmd\": \"LCS\", \"summary\": \"Find longest common substring\", \"arguments\": \"key1 key2 [len] [idx] [len] [withmatchlen]\", \"since\": \"7.0.0\"}, {\"cmd\": \"LINDEX\", \"summary\": \"Get an element from a list by its index\", \"arguments\": \"key index\", \"since\": \"1.0.0\"}, {\"cmd\": \"LINSERT\", \"summary\": \"Insert an element before or after another element in a list\", \"arguments\": \"key where pivot element\", \"since\": \"2.2.0\"}, {\"cmd\": \"LLEN\", \"summary\": \"Get the length of a list\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"LMOVE\", \"summary\": \"Pop an element from a list, push it to another list and return it\", \"arguments\": \"source destination wherefrom whereto\", \"since\": \"6.2.0\"}, {\"cmd\": \"LMPOP\", \"summary\": \"Pop elements from a list\", \"arguments\": \"numkeys key where [count]\", \"since\": \"7.0.0\"}, {\"cmd\": \"LOLWUT\", \"summary\": \"Display some computer art and the Redis version\", \"arguments\": \"[version]\", \"since\": \"5.0.0\"}, {\"cmd\": \"LPOP\", \"summary\": \"Remove and get the first elements in a list\", \"arguments\": \"key [count]\", \"since\": \"1.0.0\"}, {\"cmd\": \"LPOS\", \"summary\": \"Return the index of matching elements on a list\", \"arguments\": \"key element [rank] [num-matches] [len]\", \"since\": \"6.0.6\"}, {\"cmd\": \"LPUSH\", \"summary\": \"Prepend one or multiple elements to a list\", \"arguments\": \"key element\", \"since\": \"1.0.0\"}, {\"cmd\": \"LPUSHX\", \"summary\": \"Prepend an element to a list, only if the list exists\", \"arguments\": \"key element\", \"since\": \"2.2.0\"}, {\"cmd\": \"LRANGE\", \"summary\": \"Get a range of elements from a list\", \"arguments\": \"key start stop\", \"since\": \"1.0.0\"}, {\"cmd\": \"LREM\", \"summary\": \"Remove elements from a list\", \"arguments\": \"key count element\", \"since\": \"1.0.0\"}, {\"cmd\": \"LSET\", \"summary\": \"Set the value of an element in a list by its index\", \"arguments\": \"key index element\", \"since\": \"1.0.0\"}, {\"cmd\": \"LTRIM\", \"summary\": \"Trim a list to the specified range\", \"arguments\": \"key start stop\", \"since\": \"1.0.0\"}, {\"cmd\": \"MEMORY\", \"summary\": \"A container for memory diagnostics commands\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MEMORY DOCTOR\", \"summary\": \"Outputs memory problems report\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MEMORY HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MEMORY MALLOC-STATS\", \"summary\": \"Show allocator internal stats\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MEMORY PURGE\", \"summary\": \"Ask the allocator to release memory\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MEMORY STATS\", \"summary\": \"Show memory usage details\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MEMORY USAGE\", \"summary\": \"Estimate the memory usage of a key\", \"arguments\": \"key [count]\", \"since\": \"4.0.0\"}, {\"cmd\": \"MGET\", \"summary\": \"Get the values of all the given keys\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"MIGRATE\", \"summary\": \"Atomically transfer a key from a Redis instance to another one.\", \"arguments\": \"host port key_or_empty_string destination-db timeout [copy] [replace] [password] [username_password] [key]\", \"since\": \"2.6.0\"}, {\"cmd\": \"MODULE\", \"summary\": \"A container for module commands\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MODULE HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"MODULE LIST\", \"summary\": \"List all modules loaded by the server\", \"arguments\": \"\", \"since\": \"4.0.0\"}, {\"cmd\": \"MODULE LOAD\", \"summary\": \"Load a module\", \"arguments\": \"path [arg]\", \"since\": \"4.0.0\"}, {\"cmd\": \"MODULE UNLOAD\", \"summary\": \"Unload a module\", \"arguments\": \"name\", \"since\": \"4.0.0\"}, {\"cmd\": \"MONITOR\", \"summary\": \"Listen for all requests received by the server in real time\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"MOVE\", \"summary\": \"Move a key to another database\", \"arguments\": \"key db\", \"since\": \"1.0.0\"}, {\"cmd\": \"MSET\", \"summary\": \"Set multiple keys to multiple values\", \"arguments\": \"key_value\", \"since\": \"1.0.1\"}, {\"cmd\": \"MSETNX\", \"summary\": \"Set multiple keys to multiple values, only if none of the keys exist\", \"arguments\": \"key_value\", \"since\": \"1.0.1\"}, {\"cmd\": \"MULTI\", \"summary\": \"Mark the start of a transaction block\", \"arguments\": \"\", \"since\": \"1.2.0\"}, {\"cmd\": \"OBJECT\", \"summary\": \"A container for object introspection commands\", \"arguments\": \"\", \"since\": \"2.2.3\"}, {\"cmd\": \"OBJECT ENCODING\", \"summary\": \"Inspect the internal encoding of a Redis object\", \"arguments\": \"key\", \"since\": \"2.2.3\"}, {\"cmd\": \"OBJECT FREQ\", \"summary\": \"Get the logarithmic access frequency counter of a Redis object\", \"arguments\": \"key\", \"since\": \"4.0.0\"}, {\"cmd\": \"OBJECT HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"6.2.0\"}, {\"cmd\": \"OBJECT IDLETIME\", \"summary\": \"Get the time since a Redis object was last accessed\", \"arguments\": \"key\", \"since\": \"2.2.3\"}, {\"cmd\": \"OBJECT REFCOUNT\", \"summary\": \"Get the number of references to the value of the key\", \"arguments\": \"key\", \"since\": \"2.2.3\"}, {\"cmd\": \"PERSIST\", \"summary\": \"Remove the expiration from a key\", \"arguments\": \"key\", \"since\": \"2.2.0\"}, {\"cmd\": \"PEXPIRE\", \"summary\": \"Set a key's time to live in milliseconds\", \"arguments\": \"key milliseconds [condition]\", \"since\": \"2.6.0\"}, {\"cmd\": \"PEXPIREAT\", \"summary\": \"Set the expiration for a key as a UNIX timestamp specified in milliseconds\", \"arguments\": \"key milliseconds-timestamp [condition]\", \"since\": \"2.6.0\"}, {\"cmd\": \"PEXPIRETIME\", \"summary\": \"Get the expiration Unix timestamp for a key in milliseconds\", \"arguments\": \"key\", \"since\": \"7.0.0\"}, {\"cmd\": \"PFADD\", \"summary\": \"Adds the specified elements to the specified HyperLogLog.\", \"arguments\": \"key [element]\", \"since\": \"2.8.9\"}, {\"cmd\": \"PFCOUNT\", \"summary\": \"Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).\", \"arguments\": \"key\", \"since\": \"2.8.9\"}, {\"cmd\": \"PFDEBUG\", \"summary\": \"Internal commands for debugging HyperLogLog values\", \"arguments\": \"\", \"since\": \"2.8.9\"}, {\"cmd\": \"PFMERGE\", \"summary\": \"Merge N different HyperLogLogs into a single one.\", \"arguments\": \"destkey sourcekey\", \"since\": \"2.8.9\"}, {\"cmd\": \"PFSELFTEST\", \"summary\": \"An internal command for testing HyperLogLog values\", \"arguments\": \"\", \"since\": \"2.8.9\"}, {\"cmd\": \"PING\", \"summary\": \"Ping the server\", \"arguments\": \"[message]\", \"since\": \"1.0.0\"}, {\"cmd\": \"PSETEX\", \"summary\": \"Set the value and expiration in milliseconds of a key\", \"arguments\": \"key milliseconds value\", \"since\": \"2.6.0\"}, {\"cmd\": \"PSUBSCRIBE\", \"summary\": \"Listen for messages published to channels matching the given patterns\", \"arguments\": \"pattern\", \"since\": \"2.0.0\"}, {\"cmd\": \"PSYNC\", \"summary\": \"Internal command used for replication\", \"arguments\": \"replicationid offset\", \"since\": \"2.8.0\"}, {\"cmd\": \"PTTL\", \"summary\": \"Get the time to live for a key in milliseconds\", \"arguments\": \"key\", \"since\": \"2.6.0\"}, {\"cmd\": \"PUBLISH\", \"summary\": \"Post a message to a channel\", \"arguments\": \"channel message\", \"since\": \"2.0.0\"}, {\"cmd\": \"PUBSUB\", \"summary\": \"A container for Pub/Sun commands\", \"arguments\": \"\", \"since\": \"2.8.0\"}, {\"cmd\": \"PUBSUB CHANNELS\", \"summary\": \"List active channels\", \"arguments\": \"[pattern]\", \"since\": \"2.8.0\"}, {\"cmd\": \"PUBSUB HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"6.2.0\"}, {\"cmd\": \"PUBSUB NUMPAT\", \"summary\": \"Get the count of unique patterns pattern subscriptions\", \"arguments\": \"\", \"since\": \"2.8.0\"}, {\"cmd\": \"PUBSUB NUMSUB\", \"summary\": \"Get the count of subscribers for channels\", \"arguments\": \"[channel]\", \"since\": \"2.8.0\"}, {\"cmd\": \"PUNSUBSCRIBE\", \"summary\": \"Stop listening for messages posted to channels matching the given patterns\", \"arguments\": \"[pattern]\", \"since\": \"2.0.0\"}, {\"cmd\": \"QUIT\", \"summary\": \"Close the connection\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"RANDOMKEY\", \"summary\": \"Return a random key from the keyspace\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"READONLY\", \"summary\": \"Enables read queries for a connection to a cluster replica node\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"READWRITE\", \"summary\": \"Disables read queries for a connection to a cluster replica node\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"RENAME\", \"summary\": \"Rename a key\", \"arguments\": \"key newkey\", \"since\": \"1.0.0\"}, {\"cmd\": \"RENAMENX\", \"summary\": \"Rename a key, only if the new key does not exist\", \"arguments\": \"key newkey\", \"since\": \"1.0.0\"}, {\"cmd\": \"REPLCONF\", \"summary\": \"An internal command for configuring the replication stream\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"REPLICAOF\", \"summary\": \"Make the server a replica of another instance, or promote it as master.\", \"arguments\": \"host port\", \"since\": \"5.0.0\"}, {\"cmd\": \"RESET\", \"summary\": \"Reset the connection\", \"arguments\": \"\", \"since\": \"6.2.0\"}, {\"cmd\": \"RESTORE\", \"summary\": \"Create a key using the provided serialized value, previously obtained using DUMP.\", \"arguments\": \"key ttl serialized-value [replace] [absttl] [seconds] [frequency]\", \"since\": \"2.6.0\"}, {\"cmd\": \"RESTORE-ASKING\", \"summary\": \"An internal command for migrating keys in a cluster\", \"arguments\": \"\", \"since\": \"3.0.0\"}, {\"cmd\": \"ROLE\", \"summary\": \"Return the role of the instance in the context of replication\", \"arguments\": \"\", \"since\": \"2.8.12\"}, {\"cmd\": \"RPOP\", \"summary\": \"Remove and get the last elements in a list\", \"arguments\": \"key [count]\", \"since\": \"1.0.0\"}, {\"cmd\": \"RPOPLPUSH\", \"summary\": \"Remove the last element in a list, prepend it to another list and return it\", \"arguments\": \"source destination\", \"since\": \"1.2.0\"}, {\"cmd\": \"RPUSH\", \"summary\": \"Append one or multiple elements to a list\", \"arguments\": \"key element\", \"since\": \"1.0.0\"}, {\"cmd\": \"RPUSHX\", \"summary\": \"Append an element to a list, only if the list exists\", \"arguments\": \"key element\", \"since\": \"2.2.0\"}, {\"cmd\": \"SADD\", \"summary\": \"Add one or more members to a set\", \"arguments\": \"key member\", \"since\": \"1.0.0\"}, {\"cmd\": \"SAVE\", \"summary\": \"Synchronously save the dataset to disk\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"SCAN\", \"summary\": \"Incrementally iterate the keys space\", \"arguments\": \"cursor [pattern] [count] [type]\", \"since\": \"2.8.0\"}, {\"cmd\": \"SCARD\", \"summary\": \"Get the number of members in a set\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SCRIPT\", \"summary\": \"A container for Lua scripts management commands\", \"arguments\": \"\", \"since\": \"2.6.0\"}, {\"cmd\": \"SCRIPT DEBUG\", \"summary\": \"Set the debug mode for executed scripts.\", \"arguments\": \"mode\", \"since\": \"3.2.0\"}, {\"cmd\": \"SCRIPT EXISTS\", \"summary\": \"Check existence of scripts in the script cache.\", \"arguments\": \"sha1\", \"since\": \"2.6.0\"}, {\"cmd\": \"SCRIPT FLUSH\", \"summary\": \"Remove all the scripts from the script cache.\", \"arguments\": \"[async]\", \"since\": \"2.6.0\"}, {\"cmd\": \"SCRIPT HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"SCRIPT KILL\", \"summary\": \"Kill the script currently in execution.\", \"arguments\": \"\", \"since\": \"2.6.0\"}, {\"cmd\": \"SCRIPT LOAD\", \"summary\": \"Load the specified Lua script into the script cache.\", \"arguments\": \"script\", \"since\": \"2.6.0\"}, {\"cmd\": \"SDIFF\", \"summary\": \"Subtract multiple sets\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SDIFFSTORE\", \"summary\": \"Subtract multiple sets and store the resulting set in a key\", \"arguments\": \"destination key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SELECT\", \"summary\": \"Change the selected database for the current connection\", \"arguments\": \"index\", \"since\": \"1.0.0\"}, {\"cmd\": \"SET\", \"summary\": \"Set the string value of a key\", \"arguments\": \"key value [expiration] [condition] [get]\", \"since\": \"1.0.0\"}, {\"cmd\": \"SETBIT\", \"summary\": \"Sets or clears the bit at offset in the string value stored at key\", \"arguments\": \"key offset value\", \"since\": \"2.2.0\"}, {\"cmd\": \"SETEX\", \"summary\": \"Set the value and expiration of a key\", \"arguments\": \"key seconds value\", \"since\": \"2.0.0\"}, {\"cmd\": \"SETNX\", \"summary\": \"Set the value of a key, only if the key does not exist\", \"arguments\": \"key value\", \"since\": \"1.0.0\"}, {\"cmd\": \"SETRANGE\", \"summary\": \"Overwrite part of a string at key starting at the specified offset\", \"arguments\": \"key offset value\", \"since\": \"2.2.0\"}, {\"cmd\": \"SHUTDOWN\", \"summary\": \"Synchronously save the dataset to disk and then shut down the server\", \"arguments\": \"[nosave_save] [now] [force] [abort]\", \"since\": \"1.0.0\"}, {\"cmd\": \"SINTER\", \"summary\": \"Intersect multiple sets\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SINTERCARD\", \"summary\": \"Intersect multiple sets and return the cardinality of the result\", \"arguments\": \"numkeys key [limit]\", \"since\": \"7.0.0\"}, {\"cmd\": \"SINTERSTORE\", \"summary\": \"Intersect multiple sets and store the resulting set in a key\", \"arguments\": \"destination key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SISMEMBER\", \"summary\": \"Determine if a given value is a member of a set\", \"arguments\": \"key member\", \"since\": \"1.0.0\"}, {\"cmd\": \"SLAVEOF\", \"summary\": \"Make the server a replica of another instance, or promote it as master. Deprecated starting with Redis 5. Use REPLICAOF instead.\", \"arguments\": \"host port\", \"since\": \"1.0.0\"}, {\"cmd\": \"SLOWLOG\", \"summary\": \"A container for slow log commands\", \"arguments\": \"\", \"since\": \"2.2.12\"}, {\"cmd\": \"SLOWLOG GET\", \"summary\": \"Get the slow log's entries\", \"arguments\": \"[count]\", \"since\": \"2.2.12\"}, {\"cmd\": \"SLOWLOG HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"6.2.0\"}, {\"cmd\": \"SLOWLOG LEN\", \"summary\": \"Get the slow log's length\", \"arguments\": \"\", \"since\": \"2.2.12\"}, {\"cmd\": \"SLOWLOG RESET\", \"summary\": \"Clear all entries from the slow log\", \"arguments\": \"\", \"since\": \"2.2.12\"}, {\"cmd\": \"SMEMBERS\", \"summary\": \"Get all the members in a set\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SMISMEMBER\", \"summary\": \"Returns the membership associated with the given elements for a set\", \"arguments\": \"key member\", \"since\": \"6.2.0\"}, {\"cmd\": \"SMOVE\", \"summary\": \"Move a member from one set to another\", \"arguments\": \"source destination member\", \"since\": \"1.0.0\"}, {\"cmd\": \"SORT\", \"summary\": \"Sort the elements in a list, set or sorted set\", \"arguments\": \"key [pattern] [offset_count] [pattern] [order] [sorting] [destination]\", \"since\": \"1.0.0\"}, {\"cmd\": \"SORT_RO\", \"summary\": \"Sort the elements in a list, set or sorted set. Read-only variant of SORT.\", \"arguments\": \"key [pattern] [offset_count] [pattern] [order] [sorting]\", \"since\": \"7.0.0\"}, {\"cmd\": \"SPOP\", \"summary\": \"Remove and return one or multiple random members from a set\", \"arguments\": \"key [count]\", \"since\": \"1.0.0\"}, {\"cmd\": \"SRANDMEMBER\", \"summary\": \"Get one or multiple random members from a set\", \"arguments\": \"key [count]\", \"since\": \"1.0.0\"}, {\"cmd\": \"SREM\", \"summary\": \"Remove one or more members from a set\", \"arguments\": \"key member\", \"since\": \"1.0.0\"}, {\"cmd\": \"SSCAN\", \"summary\": \"Incrementally iterate Set elements\", \"arguments\": \"key cursor [pattern] [count]\", \"since\": \"2.8.0\"}, {\"cmd\": \"STRLEN\", \"summary\": \"Get the length of the value stored in a key\", \"arguments\": \"key\", \"since\": \"2.2.0\"}, {\"cmd\": \"SUBSCRIBE\", \"summary\": \"Listen for messages published to the given channels\", \"arguments\": \"channel\", \"since\": \"2.0.0\"}, {\"cmd\": \"SUBSTR\", \"summary\": \"Get a substring of the string stored at a key\", \"arguments\": \"key start end\", \"since\": \"1.0.0\"}, {\"cmd\": \"SUNION\", \"summary\": \"Add multiple sets\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SUNIONSTORE\", \"summary\": \"Add multiple sets and store the resulting set in a key\", \"arguments\": \"destination key\", \"since\": \"1.0.0\"}, {\"cmd\": \"SWAPDB\", \"summary\": \"Swaps two Redis databases\", \"arguments\": \"index1 index2\", \"since\": \"4.0.0\"}, {\"cmd\": \"SYNC\", \"summary\": \"Internal command used for replication\", \"arguments\": \"\", \"since\": \"1.0.0\"}, {\"cmd\": \"TIME\", \"summary\": \"Return the current server time\", \"arguments\": \"\", \"since\": \"2.6.0\"}, {\"cmd\": \"TOUCH\", \"summary\": \"Alters the last access time of a key(s). Returns the number of existing keys specified.\", \"arguments\": \"key\", \"since\": \"3.2.1\"}, {\"cmd\": \"TTL\", \"summary\": \"Get the time to live for a key in seconds\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"TYPE\", \"summary\": \"Determine the type stored at key\", \"arguments\": \"key\", \"since\": \"1.0.0\"}, {\"cmd\": \"UNLINK\", \"summary\": \"Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.\", \"arguments\": \"key\", \"since\": \"4.0.0\"}, {\"cmd\": \"UNSUBSCRIBE\", \"summary\": \"Stop listening for messages posted to the given channels\", \"arguments\": \"[channel]\", \"since\": \"2.0.0\"}, {\"cmd\": \"UNWATCH\", \"summary\": \"Forget about all watched keys\", \"arguments\": \"\", \"since\": \"2.2.0\"}, {\"cmd\": \"WAIT\", \"summary\": \"Wait for the synchronous replication of all the write commands sent in the context of the current connection\", \"arguments\": \"numreplicas timeout\", \"since\": \"3.0.0\"}, {\"cmd\": \"WATCH\", \"summary\": \"Watch the given keys to determine execution of the MULTI/EXEC block\", \"arguments\": \"key\", \"since\": \"2.2.0\"}, {\"cmd\": \"XACK\", \"summary\": \"Marks a pending message as correctly processed, effectively removing it from the pending entries list of the consumer group. Return value of the command is the number of messages successfully acknowledged, that is, the IDs we were actually able to resolve in the PEL.\", \"arguments\": \"key group id\", \"since\": \"5.0.0\"}, {\"cmd\": \"XADD\", \"summary\": \"Appends a new entry to a stream\", \"arguments\": \"key [nomkstream] [trim] id_or_auto field_value\", \"since\": \"5.0.0\"}, {\"cmd\": \"XAUTOCLAIM\", \"summary\": \"Changes (or acquires) ownership of messages in a consumer group, as if the messages were delivered to the specified consumer.\", \"arguments\": \"key group consumer min-idle-time start [count] [justid]\", \"since\": \"6.2.0\"}, {\"cmd\": \"XCLAIM\", \"summary\": \"Changes (or acquires) ownership of a message in a consumer group, as if the message was delivered to the specified consumer.\", \"arguments\": \"key group consumer min-idle-time id [ms] [ms-unix-time] [count] [force] [justid]\", \"since\": \"5.0.0\"}, {\"cmd\": \"XDEL\", \"summary\": \"Removes the specified entries from the stream. Returns the number of items actually deleted, that may be different from the number of IDs passed in case certain IDs do not exist.\", \"arguments\": \"key id\", \"since\": \"5.0.0\"}, {\"cmd\": \"XGROUP\", \"summary\": \"A container for consumer groups commands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"XGROUP CREATE\", \"summary\": \"Create a consumer group.\", \"arguments\": \"key groupname id [mkstream]\", \"since\": \"5.0.0\"}, {\"cmd\": \"XGROUP CREATECONSUMER\", \"summary\": \"Create a consumer in a consumer group.\", \"arguments\": \"key groupname consumername\", \"since\": \"6.2.0\"}, {\"cmd\": \"XGROUP DELCONSUMER\", \"summary\": \"Delete a consumer from a consumer group.\", \"arguments\": \"key groupname consumername\", \"since\": \"5.0.0\"}, {\"cmd\": \"XGROUP DESTROY\", \"summary\": \"Destroy a consumer group.\", \"arguments\": \"key groupname\", \"since\": \"5.0.0\"}, {\"cmd\": \"XGROUP HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"XGROUP SETID\", \"summary\": \"Set a consumer group to an arbitrary last delivered ID value.\", \"arguments\": \"key groupname id\", \"since\": \"5.0.0\"}, {\"cmd\": \"XINFO\", \"summary\": \"A container for stream introspection commands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"XINFO CONSUMERS\", \"summary\": \"List the consumers in a consumer group\", \"arguments\": \"key groupname\", \"since\": \"5.0.0\"}, {\"cmd\": \"XINFO GROUPS\", \"summary\": \"List the consumer groups of a stream\", \"arguments\": \"key\", \"since\": \"5.0.0\"}, {\"cmd\": \"XINFO HELP\", \"summary\": \"Show helpful text about the different subcommands\", \"arguments\": \"\", \"since\": \"5.0.0\"}, {\"cmd\": \"XINFO STREAM\", \"summary\": \"Get information about a stream\", \"arguments\": \"key [full]\", \"since\": \"5.0.0\"}, {\"cmd\": \"XLEN\", \"summary\": \"Return the number of entries in a stream\", \"arguments\": \"key\", \"since\": \"5.0.0\"}, {\"cmd\": \"XPENDING\", \"summary\": \"Return information and entries from a stream consumer group pending entries list, that are messages fetched but never acknowledged.\", \"arguments\": \"key group [filters]\", \"since\": \"5.0.0\"}, {\"cmd\": \"XRANGE\", \"summary\": \"Return a range of elements in a stream, with IDs matching the specified IDs interval\", \"arguments\": \"key start end [count]\", \"since\": \"5.0.0\"}, {\"cmd\": \"XREAD\", \"summary\": \"Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.\", \"arguments\": \"[count] [milliseconds] streams\", \"since\": \"5.0.0\"}, {\"cmd\": \"XREADGROUP\", \"summary\": \"Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.\", \"arguments\": \"group_consumer [count] [milliseconds] [noack] streams\", \"since\": \"5.0.0\"}, {\"cmd\": \"XREVRANGE\", \"summary\": \"Return a range of elements in a stream, with IDs matching the specified IDs interval, in reverse order (from greater to smaller IDs) compared to XRANGE\", \"arguments\": \"key end start [count]\", \"since\": \"5.0.0\"}, {\"cmd\": \"XSETID\", \"summary\": \"An internal command for replicating stream values\", \"arguments\": \"key last-id\", \"since\": \"5.0.0\"}, {\"cmd\": \"XTRIM\", \"summary\": \"Trims the stream to (approximately if '~' is passed) a certain size\", \"arguments\": \"key trim\", \"since\": \"5.0.0\"}, {\"cmd\": \"ZADD\", \"summary\": \"Add one or more members to a sorted set, or update its score if it already exists\", \"arguments\": \"key [condition] [comparison] [change] [increment] score_member\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZCARD\", \"summary\": \"Get the number of members in a sorted set\", \"arguments\": \"key\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZCOUNT\", \"summary\": \"Count the members in a sorted set with scores within the given values\", \"arguments\": \"key min max\", \"since\": \"2.0.0\"}, {\"cmd\": \"ZDIFF\", \"summary\": \"Subtract multiple sorted sets\", \"arguments\": \"numkeys key [withscores]\", \"since\": \"6.2.0\"}, {\"cmd\": \"ZDIFFSTORE\", \"summary\": \"Subtract multiple sorted sets and store the resulting sorted set in a new key\", \"arguments\": \"destination numkeys key\", \"since\": \"6.2.0\"}, {\"cmd\": \"ZINCRBY\", \"summary\": \"Increment the score of a member in a sorted set\", \"arguments\": \"key increment member\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZINTER\", \"summary\": \"Intersect multiple sorted sets\", \"arguments\": \"numkeys key [weight] [aggregate] [withscores]\", \"since\": \"6.2.0\"}, {\"cmd\": \"ZINTERCARD\", \"summary\": \"Intersect multiple sorted sets and return the cardinality of the result\", \"arguments\": \"numkeys key [limit]\", \"since\": \"7.0.0\"}, {\"cmd\": \"ZINTERSTORE\", \"summary\": \"Intersect multiple sorted sets and store the resulting sorted set in a new key\", \"arguments\": \"destination numkeys key [weight] [aggregate]\", \"since\": \"2.0.0\"}, {\"cmd\": \"ZLEXCOUNT\", \"summary\": \"Count the number of members in a sorted set between a given lexicographical range\", \"arguments\": \"key min max\", \"since\": \"2.8.9\"}, {\"cmd\": \"ZMPOP\", \"summary\": \"Remove and return members with scores in a sorted set\", \"arguments\": \"numkeys key where [count]\", \"since\": \"7.0.0\"}, {\"cmd\": \"ZMSCORE\", \"summary\": \"Get the score associated with the given members in a sorted set\", \"arguments\": \"key member\", \"since\": \"6.2.0\"}, {\"cmd\": \"ZPOPMAX\", \"summary\": \"Remove and return members with the highest scores in a sorted set\", \"arguments\": \"key [count]\", \"since\": \"5.0.0\"}, {\"cmd\": \"ZPOPMIN\", \"summary\": \"Remove and return members with the lowest scores in a sorted set\", \"arguments\": \"key [count]\", \"since\": \"5.0.0\"}, {\"cmd\": \"ZRANDMEMBER\", \"summary\": \"Get one or multiple random elements from a sorted set\", \"arguments\": \"key [options]\", \"since\": \"6.2.0\"}, {\"cmd\": \"ZRANGE\", \"summary\": \"Return a range of members in a sorted set\", \"arguments\": \"key min max [sortby] [rev] [offset_count] [withscores]\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZRANGEBYLEX\", \"summary\": \"Return a range of members in a sorted set, by lexicographical range\", \"arguments\": \"key min max [offset_count]\", \"since\": \"2.8.9\"}, {\"cmd\": \"ZRANGEBYSCORE\", \"summary\": \"Return a range of members in a sorted set, by score\", \"arguments\": \"key min max [withscores] [offset_count]\", \"since\": \"1.0.5\"}, {\"cmd\": \"ZRANGESTORE\", \"summary\": \"Store a range of members from sorted set into another key\", \"arguments\": \"dst src min max [sortby] [rev] [offset_count]\", \"since\": \"6.2.0\"}, {\"cmd\": \"ZRANK\", \"summary\": \"Determine the index of a member in a sorted set\", \"arguments\": \"key member\", \"since\": \"2.0.0\"}, {\"cmd\": \"ZREM\", \"summary\": \"Remove one or more members from a sorted set\", \"arguments\": \"key member\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZREMRANGEBYLEX\", \"summary\": \"Remove all members in a sorted set between the given lexicographical range\", \"arguments\": \"key min max\", \"since\": \"2.8.9\"}, {\"cmd\": \"ZREMRANGEBYRANK\", \"summary\": \"Remove all members in a sorted set within the given indexes\", \"arguments\": \"key start stop\", \"since\": \"2.0.0\"}, {\"cmd\": \"ZREMRANGEBYSCORE\", \"summary\": \"Remove all members in a sorted set within the given scores\", \"arguments\": \"key min max\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZREVRANGE\", \"summary\": \"Return a range of members in a sorted set, by index, with scores ordered from high to low\", \"arguments\": \"key start stop [withscores]\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZREVRANGEBYLEX\", \"summary\": \"Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.\", \"arguments\": \"key max min [offset_count]\", \"since\": \"2.8.9\"}, {\"cmd\": \"ZREVRANGEBYSCORE\", \"summary\": \"Return a range of members in a sorted set, by score, with scores ordered from high to low\", \"arguments\": \"key max min [withscores] [offset_count]\", \"since\": \"2.2.0\"}, {\"cmd\": \"ZREVRANK\", \"summary\": \"Determine the index of a member in a sorted set, with scores ordered from high to low\", \"arguments\": \"key member\", \"since\": \"2.0.0\"}, {\"cmd\": \"ZSCAN\", \"summary\": \"Incrementally iterate sorted sets elements and associated scores\", \"arguments\": \"key cursor [pattern] [count]\", \"since\": \"2.8.0\"}, {\"cmd\": \"ZSCORE\", \"summary\": \"Get the score associated with the given member in a sorted set\", \"arguments\": \"key member\", \"since\": \"1.2.0\"}, {\"cmd\": \"ZUNION\", \"summary\": \"Add multiple sorted sets\", \"arguments\": \"numkeys key [weight] [aggregate] [withscores]\", \"since\": \"6.2.0\"}, {\"cmd\": \"ZUNIONSTORE\", \"summary\": \"Add multiple sorted sets and store the resulting sorted set in a new key\", \"arguments\": \"destination numkeys key [weight] [aggregate]\", \"since\": \"2.0.0\"}]"
  },
  {
    "path": "src/resources/commands.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file alias=\"commands.json\">commands.json</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "src/resources/convert_commands.py",
    "content": "import json\n\n\ndef convert_redis_io_commands(path):\n    with open(path) as f:\n        commands = json.load(f)\n\n        converted = []\n        for cmd, info in commands.items():\n\n            arguments = []\n            for arg in info.get('arguments', []):\n                parts = []\n                if 'command' in arg:\n                    parts.append(arg[\"command\"])\n\n                    if type == \"enum\":\n                        parts.append(\"|\".join(arg['enum']))\n                elif \"name\" in arg:\n                    if isinstance(arg['name'], list):\n                        parts.append(\" \".join(arg['name']))\n                    else:\n                        parts.append(arg['name'])\n\n                arg_spec = \" \".join(parts)\n\n                if arg.get('optional', False):\n                    arguments.append(\"[%s]\" % arg_spec)\n                else:\n                    arguments.append(arg_spec)\n\n            converted.append({\n                \"cmd\": cmd,\n                \"summary\": info[\"summary\"],\n                \"arguments\": \" \".join(arguments),\n                \"since\": info[\"since\"]\n            })\n\n        with open(\"%s.converted\" % path, \"w\") as fres:\n            json.dump(converted, fres)\n\n\ndef detect_key_positions(path):\n    with open(path) as f:\n        commands = json.load(f)\n\n        for cmd, info in commands.items():\n            for index, arg in enumerate(info.get('arguments', [])):\n                if 'type' in arg and arg['type'] == 'key':\n                    print('{\"%s\", %s},' % (cmd, index))\n\n\nif __name__ == \"__main__\":\n    convert_redis_io_commands(\"commands.json\")\n    #detect_key_positions(\"commands_raw.json\")    \n"
  },
  {
    "path": "src/resources/flatpak/app.resp.RESP.desktop",
    "content": "[Desktop Entry]\nVersion=1.0\nName=RESP.app\nComment=Cross-platform open source database management tool for Redis ®\nType=Application\nCategories=Development;\nExec=resp\nTerminal=false\nStartupNotify=true\nKeywords=RDM;Redis;\nIcon=app.resp.RESP\n"
  },
  {
    "path": "src/resources/flatpak/app.resp.RESP.metainfo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n    <id>app.resp.RESP</id>\n    <metadata_license>CC0-1.0</metadata_license>\n    <project_license>GPL-3.0-only</project_license>\n    <name>RESP.app - GUI for Redis ®</name>\n    <summary>Cross-platform open source database management tool for Redis ®</summary>\n    <description>\n        <p>RESP (formerly RedisDesktopManager) — is a fast open source Redis ® database management application for Windows, Linux and MacOS.\n            This tool offers you an easy-to-use GUI to access your Redis ® DB and perform some basic operations: view keys as a tree, CRUD keys, execute commands via shell.\n            RESP supports SSL/TLS encryption, SSH tunnels and cloud Redis instances, such as: Amazon ElastiCache, Microsoft Azure Redis Cache and other Redis ® clouds.</p>\n    </description>\n    <url type=\"homepage\">https://resp.app/</url>\n    <launchable type=\"desktop-id\">app.resp.RESP.desktop</launchable>\n    <screenshots>\n        <screenshot type=\"default\">\n            <image type=\"source\">https://resp.app/static/img/features/all.png?v=20202</image>\n        </screenshot>\n    </screenshots>\n    <content_rating type=\"oars-1.0\"/>\n    <releases>\n        <release version=\"2022.5\" date=\"2022-10-07\"/>  \n\t    <release version=\"2022.4.2\" date=\"2022-07-28\"/>    \n        <release version=\"2022.4\" date=\"2022-06-15\"/>\n        <release version=\"2022.3\" date=\"2022-05-04\"/>\n        <release version=\"2022.2.1\" date=\"2022-04-11\"/>\n        <release version=\"2022.2\" date=\"2022-02-23\"/>\n        <release version=\"2022.1.1\" date=\"2022-02-18\"/>\n        <release version=\"2022.0\" date=\"2022-02-15\"/>\n        <release version=\"2021.10.2\" date=\"2021-12-28\"/>\n        <release version=\"2021.9\" date=\"2021-11-30\"/>\n        <release version=\"2021.8\" date=\"2021-10-26\"/>\n        <release version=\"2021.7\" date=\"2021-07-16\"/>\n    </releases>\n</component>\n"
  },
  {
    "path": "src/resources/fonts.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file>fonts/OpenSans.ttc</file>\n        <file>fonts/Inconsolata-Regular.ttf</file>        \n    </qresource>\n</RCC>\n"
  },
  {
    "path": "src/resources/icons.qrc",
    "content": "<RCC>\n<qresource prefix=\"images/dark_theme\">\n     <file alias=\"server_group.svg\">images/dark_theme/server_group.svg</file>\n    <file alias=\"search.svg\">images/dark_theme/search.svg</file>\n    <file alias=\"pub-sub-channels.svg\">images/dark_theme/pub-sub-channels.svg</file>\n    <file alias=\"list.svg\">images/dark_theme/list.svg</file>\n    <file alias=\"code_file.svg\">images/dark_theme/code_file.svg</file>\n    <file alias=\"offline.svg\">images/dark_theme/offline.svg</file>\n    <file alias=\"binary_file.svg\">images/dark_theme/binary_file.svg</file>\n    <file alias=\"alert.svg\">images/dark_theme/alert.svg</file>\n    <file alias=\"live_update.svg\">images/dark_theme/live_update.svg</file>\n    <file alias=\"bulk_operations.svg\">images/dark_theme/bulk_operations.svg</file>\n    <file alias=\"cleanup.svg\">images/dark_theme/cleanup.svg</file>\n    <file alias=\"github.svg\">images/dark_theme/github.svg</file>\n    <file alias=\"export.svg\">images/dark_theme/export.svg</file>\n    <file alias=\"database.svg\">images/dark_theme/database.svg</file>\n    <file alias=\"file.svg\">images/dark_theme/file.svg</file>\n    <file alias=\"clear.svg\">images/dark_theme/clear.svg</file>\n    <file alias=\"maximize.svg\">images/dark_theme/maximize.svg</file>\n    <file alias=\"twi.svg\">images/dark_theme/twi.svg</file>\n    <file alias=\"slowlog.svg\">images/dark_theme/slowlog.svg</file>\n    <file alias=\"server_group_open.svg\">images/dark_theme/server_group_open.svg</file>\n    <file alias=\"key.svg\">images/dark_theme/key.svg</file>\n    <file alias=\"minimize.svg\">images/dark_theme/minimize.svg</file>\n    <file alias=\"settings.svg\">images/dark_theme/settings.svg</file>\n    <file alias=\"db-copy.svg\">images/dark_theme/db-copy.svg</file>\n    <file alias=\"import.svg\">images/dark_theme/import.svg</file>\n    <file alias=\"server.svg\">images/dark_theme/server.svg</file>\n    <file alias=\"ok.svg\">images/dark_theme/ok.svg</file>\n    <file alias=\"copy_2.svg\">images/dark_theme/copy_2.svg</file>\n    <file alias=\"telegram.svg\">images/dark_theme/telegram.svg</file>\n    <file alias=\"square-half.svg\">images/dark_theme/square-half.svg</file>\n    <file alias=\"cluster.svg\">images/dark_theme/cluster.svg</file>\n    <file alias=\"server-config.svg\">images/dark_theme/server-config.svg</file>\n    <file alias=\"plus.svg\">images/dark_theme/plus.svg</file>\n    <file alias=\"document.svg\">images/dark_theme/document.svg</file>\n    <file alias=\"namespace_open.svg\">images/dark_theme/namespace_open.svg</file>\n    <file alias=\"loader.svg\">images/dark_theme/loader.svg</file>\n    <file alias=\"add.svg\">images/dark_theme/add.svg</file>\n    <file alias=\"server-stats.svg\">images/dark_theme/server-stats.svg</file>\n    <file alias=\"copy.svg\">images/dark_theme/copy.svg</file>\n    <file alias=\"refresh.svg\">images/dark_theme/refresh.svg</file>\n    <file alias=\"clients.svg\">images/dark_theme/clients.svg</file>\n    <file alias=\"wait.svg\">images/dark_theme/wait.svg</file>\n    <file alias=\"save.svg\">images/dark_theme/save.svg</file>\n    <file alias=\"back.svg\">images/dark_theme/back.svg</file>\n    <file alias=\"live_update_disable.svg\">images/dark_theme/live_update_disable.svg</file>\n    <file alias=\"sentinel.svg\">images/dark_theme/sentinel.svg</file>\n    <file alias=\"log.svg\">images/dark_theme/log.svg</file>\n    <file alias=\"delete.svg\">images/dark_theme/delete.svg</file>\n    <file alias=\"server_offline.svg\">images/dark_theme/server_offline.svg</file>\n    <file alias=\"cleanup_filtered.svg\">images/dark_theme/cleanup_filtered.svg</file>\n    <file alias=\"sort.svg\">images/dark_theme/sort.svg</file>\n    <file alias=\"namespace.svg\">images/dark_theme/namespace.svg</file>\n    <file alias=\"filter.svg\">images/dark_theme/filter.svg</file>\n    <file alias=\"console.svg\">images/dark_theme/console.svg</file>\n    <file alias=\"help.svg\">images/dark_theme/help.svg</file>\n    <file alias=\"server_2.svg\">images/dark_theme/server_2.svg</file>\n    <file alias=\"memory_usage.svg\">images/dark_theme/memory_usage.svg</file>\n    <file alias=\"ttl.svg\">images/dark_theme/ttl.svg</file>\n</qresource>\n<qresource prefix=\"images/light_theme\">\n    <file alias=\"server_group.svg\">images/light_theme/server_group.svg</file>\n    <file alias=\"search.svg\">images/light_theme/search.svg</file>\n    <file alias=\"pub-sub-channels.svg\">images/light_theme/pub-sub-channels.svg</file>\n    <file alias=\"list.svg\">images/light_theme/list.svg</file>\n    <file alias=\"code_file.svg\">images/light_theme/code_file.svg</file>\n    <file alias=\"offline.svg\">images/light_theme/offline.svg</file>\n    <file alias=\"binary_file.svg\">images/light_theme/binary_file.svg</file>\n    <file alias=\"alert.svg\">images/light_theme/alert.svg</file>\n    <file alias=\"live_update.svg\">images/light_theme/live_update.svg</file>\n    <file alias=\"bulk_operations.svg\">images/light_theme/bulk_operations.svg</file>\n    <file alias=\"cleanup.svg\">images/light_theme/cleanup.svg</file>\n    <file alias=\"github.svg\">images/light_theme/github.svg</file>\n    <file alias=\"export.svg\">images/light_theme/export.svg</file>\n    <file alias=\"database.svg\">images/light_theme/database.svg</file>\n    <file alias=\"file.svg\">images/light_theme/file.svg</file>\n    <file alias=\"clear.svg\">images/light_theme/clear.svg</file>\n    <file alias=\"maximize.svg\">images/light_theme/maximize.svg</file>\n    <file alias=\"twi.svg\">images/light_theme/twi.svg</file>\n    <file alias=\"slowlog.svg\">images/light_theme/slowlog.svg</file>\n    <file alias=\"server_group_open.svg\">images/light_theme/server_group_open.svg</file>\n    <file alias=\"key.svg\">images/light_theme/key.svg</file>\n    <file alias=\"minimize.svg\">images/light_theme/minimize.svg</file>\n    <file alias=\"settings.svg\">images/light_theme/settings.svg</file>\n    <file alias=\"db-copy.svg\">images/light_theme/db-copy.svg</file>\n    <file alias=\"import.svg\">images/light_theme/import.svg</file>\n    <file alias=\"server.svg\">images/light_theme/server.svg</file>\n    <file alias=\"ok.svg\">images/light_theme/ok.svg</file>\n    <file alias=\"copy_2.svg\">images/light_theme/copy_2.svg</file>\n    <file alias=\"telegram.svg\">images/light_theme/telegram.svg</file>\n    <file alias=\"square-half.svg\">images/light_theme/square-half.svg</file>\n    <file alias=\"cluster.svg\">images/light_theme/cluster.svg</file>\n    <file alias=\"server-config.svg\">images/light_theme/server-config.svg</file>\n    <file alias=\"plus.svg\">images/light_theme/plus.svg</file>\n    <file alias=\"document.svg\">images/light_theme/document.svg</file>\n    <file alias=\"namespace_open.svg\">images/light_theme/namespace_open.svg</file>\n    <file alias=\"loader.svg\">images/light_theme/loader.svg</file>\n    <file alias=\"add.svg\">images/light_theme/add.svg</file>\n    <file alias=\"server-stats.svg\">images/light_theme/server-stats.svg</file>\n    <file alias=\"copy.svg\">images/light_theme/copy.svg</file>\n    <file alias=\"refresh.svg\">images/light_theme/refresh.svg</file>\n    <file alias=\"clients.svg\">images/light_theme/clients.svg</file>\n    <file alias=\"wait.svg\">images/light_theme/wait.svg</file>\n    <file alias=\"save.svg\">images/light_theme/save.svg</file>\n    <file alias=\"back.svg\">images/light_theme/back.svg</file>\n    <file alias=\"live_update_disable.svg\">images/light_theme/live_update_disable.svg</file>\n    <file alias=\"sentinel.svg\">images/light_theme/sentinel.svg</file>\n    <file alias=\"log.svg\">images/light_theme/log.svg</file>\n    <file alias=\"delete.svg\">images/light_theme/delete.svg</file>\n    <file alias=\"server_offline.svg\">images/light_theme/server_offline.svg</file>\n    <file alias=\"cleanup_filtered.svg\">images/light_theme/cleanup_filtered.svg</file>\n    <file alias=\"sort.svg\">images/light_theme/sort.svg</file>\n    <file alias=\"namespace.svg\">images/light_theme/namespace.svg</file>\n    <file alias=\"filter.svg\">images/light_theme/filter.svg</file>\n    <file alias=\"console.svg\">images/light_theme/console.svg</file>\n    <file alias=\"help.svg\">images/light_theme/help.svg</file>\n    <file alias=\"server_2.svg\">images/light_theme/server_2.svg</file>\n    <file alias=\"memory_usage.svg\">images/light_theme/memory_usage.svg</file>\n    <file alias=\"ttl.svg\">images/light_theme/ttl.svg</file>\n</qresource>\n</RCC>"
  },
  {
    "path": "src/resources/icons_qrc_generator.py",
    "content": "import glob\nimport os\n\nDARK_THEME_PATH = \"images/dark_theme\"\nLIGHT_THEME_PATH = \"images/light_theme\"\n\n\ndef generate_qrc():\n    with open(\"icons.qrc\", \"w\") as output:\n        dark_lines = []\n        light_lines = []\n        for icon_file in glob.glob(\"./%s/*.svg\" % DARK_THEME_PATH):\n            base_name = os.path.basename(icon_file)\n\n            if not os.path.exists(\"./%s/%s\" % (LIGHT_THEME_PATH, base_name)):\n                print(\"Icon %s doesn't exist in light theme\" % base_name)\n                return\n\n            dark_lines.append(f\"<file alias=\\\"{base_name}\\\">{DARK_THEME_PATH}/{base_name}</file>\")\n            light_lines.append(f\"<file alias=\\\"{base_name}\\\">{LIGHT_THEME_PATH}/{base_name}</file>\")\n\n        output.write(\"<RCC>\\n\")\n        output.write(f\"<qresource prefix=\\\"{DARK_THEME_PATH}\\\">\\n     \")\n        output.write(\"\\n    \".join(dark_lines))\n        output.write(\"\\n</qresource>\\n\")\n        output.write(f\"<qresource prefix=\\\"{LIGHT_THEME_PATH}\\\">\\n    \")\n        output.write(\"\\n    \".join(light_lines))\n        output.write(\"\\n</qresource>\\n\")\n        output.write(\"</RCC>\")\n\n\nif __name__ == \"__main__\":\n    generate_qrc()\n"
  },
  {
    "path": "src/resources/images.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/images\">\n        <file alias=\"logo.png\">images/logo.png</file>\n        <file alias=\"logo.icns\">logo.icns</file>        \n        <file alias=\"digitalocean_logo.svg\">images/digitalocean_logo.svg</file>\n        <file alias=\"aws_logo.svg\">images/aws_logo.svg</file>\n        <file alias=\"aws_logo_white.svg\">images/aws_logo_white.svg</file>\n        <file alias=\"azure_logo.svg\">images/azure_logo.svg</file>\n        <file alias=\"heroku_logo.svg\">images/heroku_logo.svg</file>\n        <file alias=\"redisinsight.svg\">images/redisinsight.svg</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "src/resources/resp.desktop",
    "content": "[Desktop Entry]\nVersion=1.0\nName=RESP.app\nComment=Developer GUI for Redis\nType=Application\nCategories=Development;\nExec=/opt/resp_app/resp\nTerminal=false\nStartupNotify=true\nIcon=resp\nKeywords=RDM;Redis;\n"
  },
  {
    "path": "src/resources/tr.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file>translations/rdm_zh_CN.qm</file>\n        <file>translations/rdm_zh_TW.qm</file>\n        <file>translations/rdm_es_ES.qm</file>\n        <file>translations/rdm_ja_JP.qm</file>\n        <file>translations/rdm_uk_UA.qm</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "src/resources/translations/rdm.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\">\n<context>\n    <name>QObject</name>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"360\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"397\"/>\n        <source>Cannot connect to cluster node %1:%2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"408\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"435\"/>\n        <source>Cannot flush db (%1): %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>RESP</name>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"82\"/>\n        <source>Settings directory is not writable</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"84\"/>\n        <source>RESP.app can&apos;t save connections file to settings directory. Please change file permissions or restart RESP.app as administrator.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"160\"/>\n        <source>Cannot parse scan response</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"328\"/>\n        <source>Server returned unexpected response: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"106\"/>\n        <source>Cannot set TTL for key %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"81\"/>\n        <source>Cannot rename key %1: %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"125\"/>\n        <source>Cannot persist key &apos;%1&apos;. &lt;br&gt; Key does not exist or does not have an assigned TTL value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"274\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"285\"/>\n        <source>Cannot load rows for key %1: %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"42\"/>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"75\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"14\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"41\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"12\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"33\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"44\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"77\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"48\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"59\"/>\n        <source>Invalid row</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"113\"/>\n        <source>Value with the same key already exists</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"184\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"340\"/>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"151\"/>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"84\"/>\n        <source>Connection error: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"136\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"136\"/>\n        <source>Data was loaded from server partially.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"26\"/>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"38\"/>\n        <source>Cannot load key %1, connection error occurred: %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"49\"/>\n        <source>Cannot load key %1 because it doesn&apos;t exist in database. Please reload connection tree and try again.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"78\"/>\n        <source>Cannot retrieve type of the key: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"122\"/>\n        <source>Cannot open file with key value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"129\"/>\n        <source>Cannot connect to server &apos;%1&apos;. Check log for details.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"139\"/>\n        <source>Open Source version of RESP.app &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"229\"/>\n        <source>Cannot load keys: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"336\"/>\n        <source>Delete key error: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"477\"/>\n        <source>Cannot determine amount of used memory by key: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"416\"/>\n        <source>Cannot flush database: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/common/tabmodel.cpp\" line=\"43\"/>\n        <source>Invalid Connection. Check connection settings.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"251\"/>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carefully or change limit in settings.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"304\"/>\n        <source>Key was added. Do you want to reload keys in selected database?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"312\"/>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"143\"/>\n        <source>Key was added</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"327\"/>\n        <source>Do you really want to remove all keys from this database?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"73\"/>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"75\"/>\n        <source>Cannot load databases:\n\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"250\"/>\n        <source>Live update was disabled</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"190\"/>\n        <source>Rename key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"199\"/>\n        <source>New name:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"21\"/>\n        <source>Total pages: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"45\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"222\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"358\"/>\n        <source>Size: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"228\"/>\n        <source>TTL:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"233\"/>\n        <source>Set key TTL</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"242\"/>\n        <source>New TTL:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"298\"/>\n        <source>Delete</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"23\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"303\"/>\n        <source>Delete key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"38\"/>\n        <source>Changes are not saved</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"39\"/>\n        <source>Do you want to close key tab without saving changes?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"260\"/>\n        <source>Persist key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"304\"/>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"153\"/>\n        <source>Do you really want to delete this key?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"140\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"318\"/>\n        <source>Reload Value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"22\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"31\"/>\n        <source>Add Row</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"30\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"526\"/>\n        <source>Add Element to HLL</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"68\"/>\n        <source>Add</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"101\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"122\"/>\n        <source>Delete row</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"107\"/>\n        <source>The row is the last one in the key. After removing it key will be deleted.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"109\"/>\n        <source>Do you really want to remove this row?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"164\"/>\n        <source>Search on page...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"191\"/>\n        <source>Full Search</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"191\"/>\n        <source>Value and Console tabs related to this connection will be closed. Do you want to continue?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"204\"/>\n        <source>Do you really want to delete connection?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"13\"/>\n        <source>Connected to cluster.\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"16\"/>\n        <source>Connected.\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"60\"/>\n        <source>Switch to %1 mode. Close console tab to stop listen for messages.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"69\"/>\n        <source>Subscribe error: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/server-actions/serverstatsmodel.cpp\" line=\"36\"/>\n        <source>Server %0</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"109\"/>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"147\"/>\n        <source>Can&apos;t find formatter: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"115\"/>\n        <source>Invalid callback</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"204\"/>\n        <source>Can&apos;t load list of available formatters from extension server: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"260\"/>\n        <source>Can&apos;t encode value: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"26\"/>\n        <source>Loading key: %1 from db %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"68\"/>\n        <source>Cannot open value tab</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"97\"/>\n        <source>Connection error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"115\"/>\n        <source>Connection error. Can&apos;t open value tab. </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"176\"/>\n        <source>Cannot reload key value: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"228\"/>\n        <source>Cannot load key value: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"29\"/>\n        <source>Connect to Redis Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"117\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"205\"/>\n        <source>Import</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"50\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"69\"/>\n        <source>Import Connections</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"58\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"74\"/>\n        <source>Export Connections</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"100\"/>\n        <source>Report issue</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"107\"/>\n        <source>Documentation</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"114\"/>\n        <source>Join Telegram Chat</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"121\"/>\n        <source>Follow</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"128\"/>\n        <source>Star on GitHub!</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"136\"/>\n        <source>Log</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"144\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"13\"/>\n        <source>Extension Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"154\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"13\"/>\n        <source>Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>New Connection Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"144\"/>\n        <source>How to connect</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"151\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"48\"/>\n        <source>Connection Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"180\"/>\n        <source>Create connection from Redis URL</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"233\"/>\n        <source>Learn more about Redis URL:  </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"240\"/>\n        <source>Connection guides</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"260\"/>\n        <source>Local or Public Redis</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"276\"/>\n        <source>Redis with SSL/TLS</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"292\"/>\n        <source>SSH tunnel</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"308\"/>\n        <source>UNIX socket</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"395\"/>\n        <source>Cannot figure out how to connect to your redis-server?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"401\"/>\n        <source>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Read the Docs&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;Contact Support&lt;/a&gt; or ask for help in our &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram Group&lt;/a&gt;</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"413\"/>\n        <source>Don&apos;t have running Redis?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"419\"/>\n        <source>Spin up hassle-free Redis on Digital Ocean</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"431\"/>\n        <source>Skip</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"466\"/>\n        <source>Name:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"472\"/>\n        <source>Connection Name</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"478\"/>\n        <source>Address:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"483\"/>\n        <source>redis-server host</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"494\"/>\n        <source>For better network performance please use 127.0.0.1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"507\"/>\n        <source>(Optional) redis-server authentication password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"512\"/>\n        <source>Username:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"518\"/>\n        <source>(Optional) redis-server authentication username (Redis &gt;6.0)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"526\"/>\n        <source>Security</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"573\"/>\n        <source>Public Key:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"579\"/>\n        <source>(Optional) Public Key in PEM format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"581\"/>\n        <source>Select public key in PEM format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"592\"/>\n        <source>(Optional) Private Key in PEM format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"594\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"703\"/>\n        <source>Select private key in PEM format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"599\"/>\n        <source>Authority:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"605\"/>\n        <source>(Optional) Authority in PEM format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"607\"/>\n        <source>Select authority file in PEM format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"553\"/>\n        <source>SSH Tunnel</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"630\"/>\n        <source>SSH Address:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"634\"/>\n        <source>Remote Host with SSH server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"642\"/>\n        <source>SSH User:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"648\"/>\n        <source>Valid SSH User Name</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"586\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"683\"/>\n        <source>Private Key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"701\"/>\n        <source>Path to Private Key in PEM format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"711\"/>\n        <source>&lt;b&gt;Tip:&lt;/b&gt; Use &lt;code&gt;⌘ + Shift + .&lt;/code&gt; to show hidden files and folders in dialog</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"87\"/>\n        <source>Password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"737\"/>\n        <source>SSH User Password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"759\"/>\n        <source>Enable TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"156\"/>\n        <source>Advanced Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"11\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"317\"/>\n        <source>Sign in with RESP.app account</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"231\"/>\n        <source>Renew your subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"234\"/>\n        <source>You have no active subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"237\"/>\n        <source>No internet connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"240\"/>\n        <source>Your trial has ended</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"252\"/>\n        <source>To use this version you need to renew your subscription.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"255\"/>\n        <source>Please make sure that RESP.app is not blocked by a firewall and you have an internet connection.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"257\"/>\n        <source>If you’re behind a proxy please enable </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"261\"/>\n        <source> option before sign-in.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"264\"/>\n        <source>Please purchase a subscription to continue using RESP.app.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"268\"/>\n        <source>If you have any questions please contact support </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"279\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Renew Subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"280\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"32\"/>\n        <source>Buy Subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"293\"/>\n        <source>Try Again</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"331\"/>\n        <source>Email:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"347\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"501\"/>\n        <source>Password:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"376\"/>\n        <source>Forgot password?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"393\"/>\n        <source>Application will be restarted to apply this setting.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"404\"/>\n        <source>Sign In</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"409\"/>\n        <source>Please enter email &amp; password to sign in.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"422\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"444\"/>\n        <source>Offline Activation</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"465\"/>\n        <source>Paste Activation code here</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"481\"/>\n        <source>Where can I find my activation code?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"490\"/>\n        <source>Activate</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"495\"/>\n        <source>Please enter valid activation code.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"540\"/>\n        <source>SSL / TLS</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"612\"/>\n        <source>Enable strict mode:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"657\"/>\n        <source>Use SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"667\"/>\n        <source>(Optional) Custom SSH Agent Path</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"669\"/>\n        <source>Select SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"678\"/>\n        <source>Additional configuration is required to enable SSH Agent support</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"734\"/>\n        <source>Passphrase for provided private key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"736\"/>\n        <source>Password request will be prompt prior to connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"747\"/>\n        <source>Ask for password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"796\"/>\n        <source>Keys loading</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"800\"/>\n        <source>Default filter:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"806\"/>\n        <source>Pattern which defines loaded keys from redis-server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"812\"/>\n        <source>Namespace Separator:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"819\"/>\n        <source>Separator used for namespace extraction from keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"825\"/>\n        <source>Timeouts &amp; Limits</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"829\"/>\n        <source>Connection Timeout (sec):</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"842\"/>\n        <source>Execution Timeout (sec):</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"853\"/>\n        <source>Databases discovery limit:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"867\"/>\n        <source>Cluster</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"871\"/>\n        <source>Change host on cluster redirects:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"881\"/>\n        <source>Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"885\"/>\n        <source>Default value formatter:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"901\"/>\n        <source>Auto detect (JSON / Plain Text / HEX)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"902\"/>\n        <source>Last selected</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"903\"/>\n        <source>Select formatter ...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"952\"/>\n        <source>Appearance</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"956\"/>\n        <source>Icon color:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1021\"/>\n        <source>Invalid settings detected!</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"992\"/>\n        <source>Test Connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/OkDialogOverlay.qml\" line=\"20\"/>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"111\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1029\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"163\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"319\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"903\"/>\n        <source>OK</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"294\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"508\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"402\"/>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"44\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"61\"/>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"89\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1057\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"175\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"331\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"172\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"89\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"268\"/>\n        <source>Cancel</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"48\"/>\n        <source>General</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"53\"/>\n        <source>Application will be restarted to apply these settings.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"73\"/>\n        <source>Language</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"85\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"174\"/>\n        <source>Font</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"97\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"186\"/>\n        <source>Font Size</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"110\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"124\"/>\n        <source>Dark Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"200\"/>\n        <source>Maximum Formatted Value Size</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"201\"/>\n        <source>Size in bytes</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"213\"/>\n        <source>Maximum amount of items per page</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"258\"/>\n        <source>Show only last part for namespaced keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"259\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"392\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"138\"/>\n        <source>Use system proxy settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"150\"/>\n        <source>Use system proxy only for HTTP(S) requests</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"156\"/>\n        <source>Value Editor</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"218\"/>\n        <source>Connections Tree</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"236\"/>\n        <source>Show namespaced keys on top</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"246\"/>\n        <source>Reopen namespaces on reload</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"247\"/>\n        <source>(Disable to improve treeview performance)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"270\"/>\n        <source>Limit for SCAN command</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"282\"/>\n        <source>Maximum amount of rendered child items</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"294\"/>\n        <source>Live update maximum allowed keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"306\"/>\n        <source>Live update interval (in seconds)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"61\"/>\n        <source>Server Url:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"75\"/>\n        <source>Basic Auth:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"81\"/>\n        <source>User</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"99\"/>\n        <source>Response timeout  (in seconds)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"109\"/>\n        <source>Available Data Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"117\"/>\n        <source>Reload</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"135\"/>\n        <source>Id</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"141\"/>\n        <source>Name</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"147\"/>\n        <source>Read Only</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"29\"/>\n        <source>Version</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1001\"/>\n        <source>Quick Start Guide</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"134\"/>\n        <source>Successful connection to redis-server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"137\"/>\n        <source>Can&apos;t connect to redis-server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"321\"/>\n        <source>Add Group</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"336\"/>\n        <source>Regroup connections</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"358\"/>\n        <source>Exit Regroup Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"370\"/>\n        <location filename=\"../../qml/common/PasswordInput.qml\" line=\"29\"/>\n        <source>Show password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/TreeItemDelegate.qml\" line=\"220\"/>\n        <source> (Removed)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"77\"/>\n        <source>Open Keys Filter</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"81\"/>\n        <source>Reload Keys in Database</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"85\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"30\"/>\n        <source>Add New Key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Disable Live Update</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Enable Live Update</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"95\"/>\n        <source>Open Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"98\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"32\"/>\n        <source>Analyze Used Memory</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"100\"/>\n        <source>Bulk Operations</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"121\"/>\n        <source>Flush Database</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"124\"/>\n        <source>Delete keys with filter</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"97\"/>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"127\"/>\n        <source>Set TTL for multiple keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"130\"/>\n        <source>Copy keys from this database to another</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"133\"/>\n        <source>Import keys from RDB file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"136\"/>\n        <source>Back</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"22\"/>\n        <source>Copy Key Name</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"29\"/>\n        <source>Reload Namespace</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"31\"/>\n        <source>Copy Namespace Pattern</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"33\"/>\n        <source>Delete Namespace</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"71\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"24\"/>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"14\"/>\n        <source>Disconnect</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"20\"/>\n        <source>Reload Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"24\"/>\n        <source>Unload All Data</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"28\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>Edit Connection Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"32\"/>\n        <source>Duplicate Connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"36\"/>\n        <source>Delete Connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"21\"/>\n        <source>Connecting...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"193\"/>\n        <source>Clear</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"234\"/>\n        <source>Arguments</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"240\"/>\n        <source>Description</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"246\"/>\n        <source>Available since</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"297\"/>\n        <source>Close</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"108\"/>\n        <source>View Server Info</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"130\"/>\n        <source>Redis Version</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"169\"/>\n        <source>Used memory</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"182\"/>\n        <source>Cmd Processed</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"203\"/>\n        <source>Monitor Commands</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"242\"/>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"319\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"105\"/>\n        <source>Clients</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"377\"/>\n        <source>Server Actions</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"143\"/>\n        <source>Uptime</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"156\"/>\n        <source>Hit Ratio</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"262\"/>\n        <source>Server Stats</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"281\"/>\n        <source>Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"321\"/>\n        <source> day(s)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"53\"/>\n        <source>Commands Per Second</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"67\"/>\n        <source>Ops/s</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"91\"/>\n        <source>Connected Clients</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"129\"/>\n        <source>Memory Usage</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"141\"/>\n        <source>Mb</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"170\"/>\n        <source>Network Input</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"182\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"218\"/>\n        <source>Kb/s</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"206\"/>\n        <source>Network Output</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"242\"/>\n        <source>Total Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"256\"/>\n        <source>Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"36\"/>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"28\"/>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"39\"/>\n        <source>Auto Refresh</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"66\"/>\n        <source>Property</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"72\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"22\"/>\n        <source>Value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"68\"/>\n        <source>Subscribe in Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"221\"/>\n        <source>Slowlog</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"299\"/>\n        <source>Pub/Sub Channels</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"38\"/>\n        <source>Enable</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"57\"/>\n        <source>Channel Name</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"54\"/>\n        <source>Command</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"71\"/>\n        <source>Processed at</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"86\"/>\n        <source>Execution Time (μs)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"51\"/>\n        <source>Client Address</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"57\"/>\n        <source>Age (sec)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"63\"/>\n        <source>Idle</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"69\"/>\n        <source>Flags</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"75\"/>\n        <source>Current Database</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"10\"/>\n        <source>Add New Key to </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"56\"/>\n        <location filename=\"../../qml/value-editor/editors/HashItemEditor.qml\" line=\"17\"/>\n        <source>Key:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"66\"/>\n        <source>Type:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"107\"/>\n        <source>Or Import Value from the file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"113\"/>\n        <source>(Optional) Any file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"115\"/>\n        <source>Select file with value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"39\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"127\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"254\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"616\"/>\n        <source>Save</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Edit Connections Group</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Add New Connections Group</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"29\"/>\n        <source>Group Name:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1091\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/editors/formatters/ValueFormatters.qml\" line=\"251\"/>\n        <source>Error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"12\"/>\n        <source>Page</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"68\"/>\n        <source>Enter valid value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"286\"/>\n        <source>Formatting error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"291\"/>\n        <source>Unknown formatter error (Empty response)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"363\"/>\n        <source>[Binary]</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"544\"/>\n        <source>Copy to Clipboard</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"600\"/>\n        <source>Exit </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"601\"/>\n        <source>Full Screen Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"617\"/>\n        <source>Save Changes</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"719\"/>\n        <source>Search string</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find Next</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"764\"/>\n        <source>Regex</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"750\"/>\n        <source>Cannot find more results</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"400\"/>\n        <source>Try to decompress:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"401\"/>\n        <source>Decompressed:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"491\"/>\n        <source>Cannot decompress value using </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"751\"/>\n        <source>Cannot find any results</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"899\"/>\n        <source>Binary value is too large to display</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"366\"/>\n        <source>View as:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"502\"/>\n        <source>Large value (&gt;150kB). Formatters are not available.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"18\"/>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"30\"/>\n        <source>Score</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"10\"/>\n        <source>Bulk Operations Manager</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Invalid RDB path</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Please specify valid path to RDB file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"88\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"89\"/>\n        <source>Delete keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"98\"/>\n        <source>Set TTL</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"106\"/>\n        <source>Copy keys to another database</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"107\"/>\n        <source>Copy keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"116\"/>\n        <source>Import data from rdb file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"151\"/>\n        <source>Redis Server:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"163\"/>\n        <source>Database number:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"183\"/>\n        <source>Path to RDB file:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"194\"/>\n        <source>Path to dump.rdb file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"196\"/>\n        <source>Select dump.rdb</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"205\"/>\n        <source>Select DB in RDB file:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Import keys that match &lt;b&gt;regex&lt;/b&gt;:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Key pattern:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"272\"/>\n        <source>Destination Redis Server:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"283\"/>\n        <source>Destination Redis Server Database Index:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show matched keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show Affected keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Matched keys:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Affected keys:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"368\"/>\n        <source>Bulk Operation finished.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"373\"/>\n        <source>Bulk Operation finished with errors</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"425\"/>\n        <source>Processed: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"427\"/>\n        <source>Getting list of affected keys...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"475\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1097\"/>\n        <source>Success</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"501\"/>\n        <source>Confirmation</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"502\"/>\n        <source>Do you really want to perform bulk operation?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"18\"/>\n        <source>ID</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"61\"/>\n        <source>Value (represented as JSON object)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"127\"/>\n        <source>The row has been changed on server.Reload and try again.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/bulkoperationsmanager.cpp\" line=\"131\"/>\n        <source>Failed to perform actions on %1 keys. </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"12\"/>\n        <source>Cannot copy key </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"123\"/>\n        <source>Source connection error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"135\"/>\n        <source>Target connection error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/deleteoperation.cpp\" line=\"11\"/>\n        <source>Cannot remove key </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"17\"/>\n        <source>Cannot execute command </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"26\"/>\n        <source>Invalid regexp for keys filter.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"39\"/>\n        <source>Cannot get the list of affected keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/ttloperation.cpp\" line=\"11\"/>\n        <source>Cannot set TTL for key </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/abstractnamespaceitem.cpp\" line=\"381\"/>\n        <source>Your redis-server doesn&apos;t support &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; commands.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"138\"/>\n        <source>Key was added. Do you want to reload keys in selected namespace?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/FilePathInput.qml\" line=\"27\"/>\n        <source>Select File</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"913\"/>\n        <source>Save value to file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Raw Value to File</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Formatted Value to File</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Raw Value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Formatted Value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"51\"/>\n        <source>Value was saved to file:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/abstractoperation.cpp\" line=\"38\"/>\n        <source>Cannot connect to redis-server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"13\"/>\n        <source>Edit Connection Group</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"17\"/>\n        <source>Delete Connection Group</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/servergroup.cpp\" line=\"58\"/>\n        <source>Do you really want to delete group &lt;b&gt;with all connections&lt;/b&gt;?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"8\"/>\n        <source>Order of elements:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"20\"/>\n        <source>Default</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"21\"/>\n        <source>Reverse</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"28\"/>\n        <source>Start date should be less than End date</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"136\"/>\n        <source>Apply filter</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"30\"/>\n        <source>&lt;span style=&quot;font-size: 11px;&quot;&gt;Powered by awesome &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;open-source software&lt;/a&gt; and &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"11\"/>\n        <source>Getting Started</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"42\"/>\n        <source>Thank you for choosing RESP.app. Let&apos;s make your Redis experience better.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"60\"/>\n        <source>Connect to Redis-Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"73\"/>\n        <source>Read the Docs</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/loadmoreitem.cpp\" line=\"12\"/>\n        <source>Load more keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"19\"/>\n        <source>SSH Passphrase</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"21\"/>\n        <source>Unknown</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <source>Passphrase</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"71\"/>\n        <source>Continue</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"24\"/>\n        <source>Yes</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"32\"/>\n        <source>No</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"19\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"25\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"49\"/>\n        <source>Trial is active till</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"58\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"59\"/>\n        <source>Licensed to</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"66\"/>\n        <source>Subscription is active until:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Manage Subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"147\"/>\n        <source>Network is not accessible. Please ensure that you have internet access and try again.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"190\"/>\n        <source>Invalid login or password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"200\"/>\n        <source>Too many requests from your IP</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"206\"/>\n        <source>Unknown error. Status code %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"321\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"734\"/>\n        <source>Cannot parse server reply</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"341\"/>\n        <source>Cannot validate token</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"347\"/>\n        <source>Cannot login - %1. &lt;br/&gt; Please try again or contact  &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"588\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"614\"/>\n        <source>Cannot save the update. Disk is full or download folder is not writable.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"666\"/>\n        <source>Download was canceled</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"673\"/>\n        <source>Network error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"718\"/>\n        <source>Expired activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"731\"/>\n        <source>Invalid activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/ColorInput.qml\" line=\"43\"/>\n        <source>Select</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/UnsupportedDataType.qml\" line=\"24\"/>\n        <source>Unsupported Redis Data type </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"163\"/>\n        <source>Cannot delete key:\n\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "src/resources/translations/rdm_es_ES.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"es_ES\">\n<context>\n    <name>QObject</name>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"360\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"397\"/>\n        <source>Cannot connect to cluster node %1:%2</source>\n        <translation>No se puede conectar al nodo del cluster %1:%2</translation>\n    </message>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"408\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"435\"/>\n        <source>Cannot flush db (%1): %2</source>\n        <translation>No se puede vaciar db (%1): %2</translation>\n    </message>\n</context>\n<context>\n    <name>RESP</name>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"82\"/>\n        <source>Settings directory is not writable</source>\n        <translation>El directorio de ajustes no es grabable</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"84\"/>\n        <source>RESP.app can&apos;t save connections file to settings directory. Please change file permissions or restart RESP.app as administrator.</source>\n        <translation>RESP.app no puede guardar las conexiones en el directorio de configuraciones. Cambia los permisos o reinicia RESP.app como administrador.</translation>\n    </message>\n    <message>\n        <source>RDM can&apos;t save connections file to settings directory. Please change file permissions or restart RDM as administrator.</source>\n        <translation type=\"vanished\">RDM no puede grabar el fichero de conexiones en el directorio de ajustes. Por favor cambia los permisos del fichero o reinicia RDM como administrador.</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"81\"/>\n        <source>Cannot rename key %1: %2</source>\n        <translation>No se puede renombrar la clave %1: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"125\"/>\n        <source>Cannot persist key &apos;%1&apos;. &lt;br&gt; Key does not exist or does not have an assigned TTL value</source>\n        <translation>La clave &apos;%1&apos; no se pudo persistir. &lt;br&gt; La llave no parece existir o tener un tiempo de espera asociado</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"160\"/>\n        <source>Cannot parse scan response</source>\n        <translation>No se puede interpretar la respuesta</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"328\"/>\n        <source>Server returned unexpected response: </source>\n        <translation>El servidor ha devuelto una respuesta inesperada: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"106\"/>\n        <source>Cannot set TTL for key %1</source>\n        <translation>No se puede asignar el TTL para la clave %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"274\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"285\"/>\n        <source>Cannot load rows for key %1: %2</source>\n        <translation>No se pueden cargar las filas para la clave %1: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"42\"/>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"75\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"14\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"41\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"12\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"33\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"44\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"77\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"48\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"59\"/>\n        <source>Invalid row</source>\n        <translation>Fila inválida</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"113\"/>\n        <source>Value with the same key already exists</source>\n        <translation>Ya existe un valor con la misma clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"184\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"340\"/>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"151\"/>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"84\"/>\n        <source>Connection error: </source>\n        <translation>Error de conexión: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"136\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"136\"/>\n        <source>Data was loaded from server partially.</source>\n        <translation>Los datos se cargaron parcialmente desde el servidor.</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"26\"/>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"38\"/>\n        <source>Cannot load key %1, connection error occurred: %2</source>\n        <translation>No se puede cargar la clave %1, error de conexión: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"49\"/>\n        <source>Cannot load key %1 because it doesn&apos;t exist in database. Please reload connection tree and try again.</source>\n        <translation>No se puede cargar la clave %1 porque no existe en la base de datos. Por favor recarga el árbol de conexión e inténtalo de nuevo.</translation>\n    </message>\n    <message>\n        <source>Cannot load TTL for key %1, connection error occurred: %2</source>\n        <translation type=\"vanished\">No se puede cargar el TTL para la clave %1, error de conexión: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"78\"/>\n        <source>Cannot retrieve type of the key: </source>\n        <translation>No se puede recuperar el tipo de la clave: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"122\"/>\n        <source>Cannot open file with key value</source>\n        <translation>No se puede abrir el fichero con clave valor</translation>\n    </message>\n    <message>\n        <source>Unsupported Redis Data type %1</source>\n        <translation type=\"vanished\">Tipo de datos Redis no soportado %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"129\"/>\n        <source>Cannot connect to server &apos;%1&apos;. Check log for details.</source>\n        <translation>No se puede conectar al servidor %1. Compruba el log para más detalles.</translation>\n    </message>\n    <message>\n        <source>Open Source version of RDM &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation type=\"vanished\">La versión Open Source de RDM &lt;b&gt;no soporta túneles SSH&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; Para obtener todas las características, por favor, compra un suscripción en &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Cada suscripción individual nos proporciona fondos para continuar el proceso de desarrollo y proporcionar soporte a nuestros usuarios. &lt;br /&gt;Si tienes alguna pregunta, por favor, contacta con nosotros en &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Chat Telegram&lt;/a&gt;.</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"139\"/>\n        <source>Open Source version of RESP.app &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"229\"/>\n        <source>Cannot load keys: %1</source>\n        <translation>No se pueden cargar las claves: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"336\"/>\n        <source>Delete key error: %1</source>\n        <translation>Error: %1 borrando clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"477\"/>\n        <source>Cannot determine amount of used memory by key: %1</source>\n        <translation>No se puede determinar la memoria usada por la clave: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"416\"/>\n        <source>Cannot flush database: </source>\n        <translation>No se puede vaciar la base de datos: </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/common/tabmodel.cpp\" line=\"43\"/>\n        <source>Invalid Connection. Check connection settings.</source>\n        <translation>Conexión inválida. Comprueba ajustes de conexión.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"251\"/>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carefully or change limit in settings.</source>\n        <translation>Actualización automática se ha desactivado al superarse el límite de claves. Por favor especifica un filtro más restrictivo o cambia el límite en ajustes.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"304\"/>\n        <source>Key was added. Do you want to reload keys in selected database?</source>\n        <translation>Clave añadida. ¿Quieres recargar las claves en la base de datos seleccionada?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"312\"/>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"143\"/>\n        <source>Key was added</source>\n        <translation>Clave añadida</translation>\n    </message>\n    <message>\n        <source>Another operation is currently in progress</source>\n        <translation type=\"vanished\">Hay otra operación en progreso actualmente</translation>\n    </message>\n    <message>\n        <source>Please wait until another operation will be finished.</source>\n        <translation type=\"vanished\">Por favor espera hasta que la otra operación finalice.</translation>\n    </message>\n    <message>\n        <source>Please wait until another operation will be finised.</source>\n        <translation type=\"vanished\">Por favor espera hasta que la otra operación finalice.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"327\"/>\n        <source>Do you really want to remove all keys from this database?</source>\n        <translation>¿Seguro que quieres borrar todas las claves de esta base de datos?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"73\"/>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"75\"/>\n        <source>Cannot load databases:\n\n</source>\n        <translation>No se pueden cargar bases de datos:\n\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"250\"/>\n        <source>Live update was disabled</source>\n        <translation>Actualización automática se ha desactivado</translation>\n    </message>\n    <message>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carrfully or change limit in settings.</source>\n        <translation type=\"vanished\">Actualización automática se ha desactivado al superarse el límite de claves. Por favor especifica un filtro más restrictivo o cambia el límite en ajustes.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"190\"/>\n        <source>Rename key</source>\n        <translation>Renombrar la clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"199\"/>\n        <source>New name:</source>\n        <translation>Nuevo nombre:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"21\"/>\n        <source>Total pages: </source>\n        <translation>Total de páginas: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"45\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"222\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"358\"/>\n        <source>Size: </source>\n        <translation>Tamaño: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"228\"/>\n        <source>TTL:</source>\n        <translation>TTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"233\"/>\n        <source>Set key TTL</source>\n        <translation>Ajusta TTL de la clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"242\"/>\n        <source>New TTL:</source>\n        <translation>Nuevo TTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"298\"/>\n        <source>Delete</source>\n        <translation>Borrar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"23\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"303\"/>\n        <source>Delete key</source>\n        <translation>Borrar Clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"38\"/>\n        <source>Changes are not saved</source>\n        <translation>Cambios no guardados</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"39\"/>\n        <source>Do you want to close key tab without saving changes?</source>\n        <translation>¿Quieres cerrar la pestaña de claves sin guardar los cambios?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"260\"/>\n        <source>Persist key</source>\n        <translation>Persistir clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"304\"/>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"153\"/>\n        <source>Do you really want to delete this key?</source>\n        <translation>¿Seguro que quieres borrar esta clave?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"140\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"318\"/>\n        <source>Reload Value</source>\n        <translation>Recargar Valor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"22\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"31\"/>\n        <source>Add Row</source>\n        <translation>Añadir Fila</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"30\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"526\"/>\n        <source>Add Element to HLL</source>\n        <translation>Añadir Elemento a HLL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"68\"/>\n        <source>Add</source>\n        <translation>Añadir</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"101\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"122\"/>\n        <source>Delete row</source>\n        <translation>Borrar Fila</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"107\"/>\n        <source>The row is the last one in the key. After removing it key will be deleted.</source>\n        <translation>Esta fila es la última en la clave. Después de borrarla, la clave será borrada.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"109\"/>\n        <source>Do you really want to remove this row?</source>\n        <translation>¿Seguro que quieres borrar esta fila?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"164\"/>\n        <source>Search on page...</source>\n        <translation>Buscar en la página...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"191\"/>\n        <source>Full Search</source>\n        <translation>Búsqueda Completa</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"191\"/>\n        <source>Value and Console tabs related to this connection will be closed. Do you want to continue?</source>\n        <translation>Las pestañas de Valor y Consola relacionadas con esta conexión deben cerrarse. ¿Quieres continuar?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"204\"/>\n        <source>Do you really want to delete connection?</source>\n        <translation>¿Seguro que quieres borrar la conexión?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"13\"/>\n        <source>Connected to cluster.\n</source>\n        <translation>Conectado al cluster.\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"16\"/>\n        <source>Connected.\n</source>\n        <translation>Conectado.\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"60\"/>\n        <source>Switch to %1 mode. Close console tab to stop listen for messages.</source>\n        <translation>Cambio a modo %1. Cierra la pestaña de consola para dejar de escuchar mensajes.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"69\"/>\n        <source>Subscribe error: %1</source>\n        <translation>Error de suscripción: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/server-actions/serverstatsmodel.cpp\" line=\"36\"/>\n        <source>Server %0</source>\n        <translation>Servidor %0</translation>\n    </message>\n    <message>\n        <source>Can&apos;t find formatter with name: %1</source>\n        <translation type=\"vanished\">No se encuentra un formateador con el nombre: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"109\"/>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"147\"/>\n        <source>Can&apos;t find formatter: %1</source>\n        <translation>No puede encontrar el formateador: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"115\"/>\n        <source>Invalid callback</source>\n        <translation>Llamada de retorno inválida</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"204\"/>\n        <source>Can&apos;t load list of available formatters from extension server: %1</source>\n        <translation>No puede cargar la lista de formateadores del servidor de extensión: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"260\"/>\n        <source>Can&apos;t encode value: %1</source>\n        <translation>No puede codificar el valor: %1</translation>\n    </message>\n    <message>\n        <source>Cannot decode value using %1 formatter. </source>\n        <translation type=\"vanished\">No se puede decodificar el valor usando el formateador %1. </translation>\n    </message>\n    <message>\n        <source>Cannot validate value using %1 formatter.</source>\n        <translation type=\"vanished\">No se puede validar el valor usando el formateador %1.</translation>\n    </message>\n    <message>\n        <source>Cannot encode value using %1 formatter. </source>\n        <translation type=\"vanished\">No se puede codificar el valor usando el formateador %1. </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"26\"/>\n        <source>Loading key: %1 from db %2</source>\n        <translation>Cargando clave: %1 desde db %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"68\"/>\n        <source>Cannot open value tab</source>\n        <translation>No se puede abrir la pestaña de valores</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"97\"/>\n        <source>Connection error</source>\n        <translation>Error de conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"115\"/>\n        <source>Connection error. Can&apos;t open value tab. </source>\n        <translation>Error de conexión. No se puede abrir la pestaña de valores. </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"176\"/>\n        <source>Cannot reload key value: %1</source>\n        <translation>No se puede recargar el valor de la clave: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"228\"/>\n        <source>Cannot load key value: %1</source>\n        <translation>No se puede cargar el valor de la clave: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"29\"/>\n        <source>Connect to Redis Server</source>\n        <translation>Conectar a servidor Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"117\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"205\"/>\n        <source>Import</source>\n        <translation>Importar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"50\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"69\"/>\n        <source>Import Connections</source>\n        <translation>Importar Conexiones</translation>\n    </message>\n    <message>\n        <source>Export</source>\n        <translation type=\"vanished\">Exportar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"58\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"74\"/>\n        <source>Export Connections</source>\n        <translation>Exportar Conexiones</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"100\"/>\n        <source>Report issue</source>\n        <translation>Informar de un problema</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"107\"/>\n        <source>Documentation</source>\n        <translation>Documentación</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"114\"/>\n        <source>Join Telegram Chat</source>\n        <translation>Unirse al chat de Telegram</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"121\"/>\n        <source>Follow</source>\n        <translation>Seguir</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"128\"/>\n        <source>Star on GitHub!</source>\n        <translation>¡Estrella en GitHub!</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"136\"/>\n        <source>Log</source>\n        <translation>Log</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"144\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"13\"/>\n        <source>Extension Server</source>\n        <translation>Servidor de extensión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"154\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"13\"/>\n        <source>Settings</source>\n        <translation>Ajustes</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>New Connection Settings</source>\n        <translation>Ajustes de Nueva Conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"144\"/>\n        <source>How to connect</source>\n        <translation>Cómo conectar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"151\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"48\"/>\n        <source>Connection Settings</source>\n        <translation>Ajustes de Conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"180\"/>\n        <source>Create connection from Redis URL</source>\n        <translation>Crear conexión desde Redis URL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"233\"/>\n        <source>Learn more about Redis URL:  </source>\n        <translation>Aprender más acerca de Redis URL:  </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"240\"/>\n        <source>Connection guides</source>\n        <translation>Guías de conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"260\"/>\n        <source>Local or Public Redis</source>\n        <translation>Redis Público o Local</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"276\"/>\n        <source>Redis with SSL/TLS</source>\n        <translation>Redis con SSL/TLS</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"292\"/>\n        <source>SSH tunnel</source>\n        <translation>Túnel SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"308\"/>\n        <source>UNIX socket</source>\n        <translation>Socket UNIX</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"395\"/>\n        <source>Cannot figure out how to connect to your redis-server?</source>\n        <translation>¿No sabe cómo conectar a su servidor redis?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"401\"/>\n        <source>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Read the Docs&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;Contact Support&lt;/a&gt; or ask for help in our &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram Group&lt;/a&gt;</source>\n        <translation>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Leer la documentación&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;Contactar Soporte&lt;/a&gt; o pedir ayuda en nuestro &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Grupo de Telegram&lt;/a&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"413\"/>\n        <source>Don&apos;t have running Redis?</source>\n        <translation>¿No tiene Redis en ejecución?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"419\"/>\n        <source>Spin up hassle-free Redis on Digital Ocean</source>\n        <translation>Lanza Redis sin complicaciones en Digital Ocean</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"431\"/>\n        <source>Skip</source>\n        <translation>Omitir</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"466\"/>\n        <source>Name:</source>\n        <translation>Nombre:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"472\"/>\n        <source>Connection Name</source>\n        <translation>Nombre de Conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"478\"/>\n        <source>Address:</source>\n        <translation>Dirección:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"483\"/>\n        <source>redis-server host</source>\n        <translation>host Redis Server</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"494\"/>\n        <source>For better network performance please use 127.0.0.1</source>\n        <translation>Para obtener un mejor despempeño usa 127.0.0.1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"507\"/>\n        <source>(Optional) redis-server authentication password</source>\n        <translation>(Opcional) Contraseña Redis server</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"512\"/>\n        <source>Username:</source>\n        <translation>Nombre de usuario:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"518\"/>\n        <source>(Optional) redis-server authentication username (Redis &gt;6.0)</source>\n        <translation>(Opcional) Nombre de usuario de autenticación de redis-server (Redis &gt;6.0)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"526\"/>\n        <source>Security</source>\n        <translation>Seguridad</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"573\"/>\n        <source>Public Key:</source>\n        <translation>Clave Pública:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"579\"/>\n        <source>(Optional) Public Key in PEM format</source>\n        <translation>(Opcional) Clave Pública en formato PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"581\"/>\n        <source>Select public key in PEM format</source>\n        <translation>Selecciona clave pública en formato PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"592\"/>\n        <source>(Optional) Private Key in PEM format</source>\n        <translation>(Opcional) Clave Privada en formato PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"594\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"703\"/>\n        <source>Select private key in PEM format</source>\n        <translation>Selecciona clave privada en formato PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"599\"/>\n        <source>Authority:</source>\n        <translation>Autoridad:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"605\"/>\n        <source>(Optional) Authority in PEM format</source>\n        <translation>(Opcional) Autoridad en formato PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"607\"/>\n        <source>Select authority file in PEM format</source>\n        <translation>Selecciona autoridad en formato PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"612\"/>\n        <source>Enable strict mode:</source>\n        <translation>Activar modo estricto:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"553\"/>\n        <source>SSH Tunnel</source>\n        <translation>Túnel SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"630\"/>\n        <source>SSH Address:</source>\n        <translation>Dirección SSH:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"634\"/>\n        <source>Remote Host with SSH server</source>\n        <translation>Host Remoto con servidor SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"642\"/>\n        <source>SSH User:</source>\n        <translation>Usuario SSH:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"648\"/>\n        <source>Valid SSH User Name</source>\n        <translation>Nombre de Usuario SSH Válido</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"711\"/>\n        <source>&lt;b&gt;Tip:&lt;/b&gt; Use &lt;code&gt;⌘ + Shift + .&lt;/code&gt; to show hidden files and folders in dialog</source>\n        <translation>&lt;b&gt;Tip:&lt;/b&gt; Use &lt;code&gt;⌘ + Shift + .&lt;/code&gt; para mostrar ficheros y carpetas ocultas en el diálogo</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"586\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"683\"/>\n        <source>Private Key</source>\n        <translation>Clave Privada</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"701\"/>\n        <source>Path to Private Key in PEM format</source>\n        <translation>Ruta a la Clave Privada en formato PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"87\"/>\n        <source>Password</source>\n        <translation>Contraseña</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"737\"/>\n        <source>SSH User Password</source>\n        <translation>Contraseña Usuario SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"759\"/>\n        <source>Enable TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</source>\n        <translation>Activar TLS sobre SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encriptación en tránsito&lt;/b&gt;)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"156\"/>\n        <source>Advanced Settings</source>\n        <translation>Ajustes Avanzados</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"540\"/>\n        <source>SSL / TLS</source>\n        <translation>SSL / TLS</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"657\"/>\n        <source>Use SSH Agent</source>\n        <translation>Usar Agente SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"667\"/>\n        <source>(Optional) Custom SSH Agent Path</source>\n        <translation>(Opcional) Mofificar Ruta del Agente SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"669\"/>\n        <source>Select SSH Agent</source>\n        <translation>Seleccionar Agente SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"678\"/>\n        <source>Additional configuration is required to enable SSH Agent support</source>\n        <translation>Configuración adicional es requerida para habilitar el soporte de Agente SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"734\"/>\n        <source>Passphrase for provided private key</source>\n        <translation>Contraseña para clave privada proporcionada</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"736\"/>\n        <source>Password request will be prompt prior to connection</source>\n        <translation>Se pedirá contraseña antes de la conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"747\"/>\n        <source>Ask for password</source>\n        <translation>Pedir contraseña</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"796\"/>\n        <source>Keys loading</source>\n        <translation>Carga de claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"800\"/>\n        <source>Default filter:</source>\n        <translation>Filtro por defecto:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"806\"/>\n        <source>Pattern which defines loaded keys from redis-server</source>\n        <translation>Patrón que define las claves cargadas desde el servidor redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"812\"/>\n        <source>Namespace Separator:</source>\n        <translation>Separador de Namespace:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"819\"/>\n        <source>Separator used for namespace extraction from keys</source>\n        <translation>Separador usado para extracción de namespace de las claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"825\"/>\n        <source>Timeouts &amp; Limits</source>\n        <translation>Timeouts y Límites</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"829\"/>\n        <source>Connection Timeout (sec):</source>\n        <translation>Timeout de Conexión (seg):</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"842\"/>\n        <source>Execution Timeout (sec):</source>\n        <translation>Timeout de Ejecución (seg):</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"853\"/>\n        <source>Databases discovery limit:</source>\n        <translation>Límite de descubrimiento de Bases de datos:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"867\"/>\n        <source>Cluster</source>\n        <translation>Cluster</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"871\"/>\n        <source>Change host on cluster redirects:</source>\n        <translation>Cambiar host en redirección de cluster:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"881\"/>\n        <source>Formatters</source>\n        <translation>Formateadores</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"885\"/>\n        <source>Default value formatter:</source>\n        <translation>Valor por defecto del formateador:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"901\"/>\n        <source>Auto autodetect (JSON / Plain Text / HEX)</source>\n        <translation>Autodetectar (JSON / Texto Plano / HEX)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"902\"/>\n        <source>Last selected</source>\n        <translation>Último seleccionado</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"903\"/>\n        <source>Select formatter ...</source>\n        <translation>Seleccionar formateador ...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"952\"/>\n        <source>Appearance</source>\n        <translation>Apariencia</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"956\"/>\n        <source>Icon color:</source>\n        <translation>Color del ícono</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1021\"/>\n        <source>Invalid settings detected!</source>\n        <translation>Ajustes inválidos detectados!</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"992\"/>\n        <source>Test Connection</source>\n        <translation>Probar Conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/OkDialogOverlay.qml\" line=\"20\"/>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"111\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1029\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"163\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"319\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"903\"/>\n        <source>OK</source>\n        <translation>OK</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"294\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"508\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"402\"/>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"44\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"61\"/>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"89\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1057\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"175\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"331\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"172\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"89\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"268\"/>\n        <source>Cancel</source>\n        <translation>Cancelar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"48\"/>\n        <source>General</source>\n        <translation>General</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"53\"/>\n        <source>Application will be restarted to apply these settings.</source>\n        <translation>La aplicación será reiniciada al aplicar las configuraciones.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"73\"/>\n        <source>Language</source>\n        <translation>Idioma</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"393\"/>\n        <source>Application will be restarted to apply this setting.</source>\n        <translation>La aplicación se reiniciara para aplicar este ajuste.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"85\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"174\"/>\n        <source>Font</source>\n        <translation>Fuente</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"97\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"186\"/>\n        <source>Font Size</source>\n        <translation>Tamaño Fuente</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"110\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"124\"/>\n        <source>Dark Mode</source>\n        <translation>Modo Oscuro</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"200\"/>\n        <source>Maximum Formatted Value Size</source>\n        <translation>Máximo tamaño de valor formateado</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"201\"/>\n        <source>Size in bytes</source>\n        <translation>Tamaño en bytes</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"259\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"392\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"138\"/>\n        <source>Use system proxy settings</source>\n        <translation>Usar ajustes del proxy del sistema</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"150\"/>\n        <source>Use system proxy only for HTTP(S) requests</source>\n        <translation>Usar proxy del sistema sólo para peticiones HTTP(S)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"156\"/>\n        <source>Value Editor</source>\n        <translation>Valor de editor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"213\"/>\n        <source>Maximum amount of items per page</source>\n        <translation>Máxima cantidad de items por página</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"218\"/>\n        <source>Connections Tree</source>\n        <translation>Árbol de conexiones</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"236\"/>\n        <source>Show namespaced keys on top</source>\n        <translation>Mostrar arriba las claves con namespace</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"246\"/>\n        <source>Reopen namespaces on reload</source>\n        <translation>Reabrir namespaces al recargar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"247\"/>\n        <source>(Disable to improve treeview performance)</source>\n        <translation>Desactivar para mejorar el rendimiento de la vista de árbol</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"258\"/>\n        <source>Show only last part for namespaced keys</source>\n        <translation>Mostrar únicamete la última parte de claves con namespace</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"270\"/>\n        <source>Limit for SCAN command</source>\n        <translation>Límite para comando SCAN</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"282\"/>\n        <source>Maximum amount of rendered child items</source>\n        <translation>Cantidad máxima de items hijos a mostrar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"294\"/>\n        <source>Live update maximum allowed keys</source>\n        <translation>Máximo de claves permitidas en actualización automática</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"306\"/>\n        <source>Live update interval (in seconds)</source>\n        <translation>Intervalo de actualización automática (en segundos)</translation>\n    </message>\n    <message>\n        <source>External Value View Formatters</source>\n        <translation type=\"vanished\">Formateadores Externos de Visor de Valores</translation>\n    </message>\n    <message>\n        <source>Formatters path: %0</source>\n        <translation type=\"vanished\">Ruta a formateadores: %0</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"61\"/>\n        <source>Server Url:</source>\n        <translation>URL del servidor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"75\"/>\n        <source>Basic Auth:</source>\n        <translation>Autorización básica</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"81\"/>\n        <source>User</source>\n        <translation>Usuario</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"99\"/>\n        <source>Response timeout  (in seconds)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"109\"/>\n        <source>Available Data Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"117\"/>\n        <source>Reload</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"135\"/>\n        <source>Id</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"141\"/>\n        <source>Name</source>\n        <translation>Nombre</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"147\"/>\n        <source>Read Only</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"29\"/>\n        <source>Version</source>\n        <translation>Versión</translation>\n    </message>\n    <message>\n        <source>Explore RDM</source>\n        <translation type=\"vanished\">Explorar RDM</translation>\n    </message>\n    <message>\n        <source>Before using RDM take a look on the %1</source>\n        <translation type=\"vanished\">Antes de usar RDM echa un vistazo en %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1001\"/>\n        <source>Quick Start Guide</source>\n        <translation>Guía de inicio rápido</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"134\"/>\n        <source>Successful connection to redis-server</source>\n        <translation>Conexión correcta a servidor redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"137\"/>\n        <source>Can&apos;t connect to redis-server</source>\n        <translation>No se puede conectar a servidor redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"321\"/>\n        <source>Add Group</source>\n        <translation>Agregar grupo</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"336\"/>\n        <source>Regroup connections</source>\n        <translation>Reagrupar conexiones</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"358\"/>\n        <source>Exit Regroup Mode</source>\n        <translation>Salir del modo de reagrupación</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"10\"/>\n        <source>Bulk Operations Manager</source>\n        <translation>Administrador de Operaciones Masivas</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Invalid RDB path</source>\n        <translation>Ruta RDB inválida</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Please specify valid path to RDB file</source>\n        <translation>Por favor, especifique una ruta válida a un fichero RDB</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"88\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"89\"/>\n        <source>Delete keys</source>\n        <translation>Borrar Claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"98\"/>\n        <source>Set TTL</source>\n        <translation>Asignar TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"106\"/>\n        <source>Copy keys to another database</source>\n        <translation>Copiar claves a otra base de datos</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"107\"/>\n        <source>Copy keys</source>\n        <translation>Copiar claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"116\"/>\n        <source>Import data from rdb file</source>\n        <translation>Importar datos desde fichero rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"151\"/>\n        <source>Redis Server:</source>\n        <translation>Servidor Redis:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"163\"/>\n        <source>Database number:</source>\n        <translation>Número de Base de Datos:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"183\"/>\n        <source>Path to RDB file:</source>\n        <translation>Ruta a fichero RDB:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"205\"/>\n        <source>Select DB in RDB file:</source>\n        <translation>Seleccione Base de Datos en fichero RDB:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Key pattern:</source>\n        <translation>Patrón de clave:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Import keys that match &lt;b&gt;regex&lt;/b&gt;:</source>\n        <translation>Importar claves que coincidan con &lt;b&gt;regex&lt;/b&gt;:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"272\"/>\n        <source>Destination Redis Server:</source>\n        <translation>Servidor Redis Destino:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"283\"/>\n        <source>Destination Redis Server Database Index:</source>\n        <translation>Índice Base de Datos Servidor Redis Destino:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show matched keys</source>\n        <translation>Mostrar claves coincidentes</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show Affected keys</source>\n        <translation>Mostrar claves afectadas</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Affected keys:</source>\n        <translation>Claves afectadas:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Matched keys:</source>\n        <translation>Claves coincidentes:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"368\"/>\n        <source>Bulk Operation finished.</source>\n        <translation>Operación Masiva finalizada.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"373\"/>\n        <source>Bulk Operation finished with errors</source>\n        <translation>Operación Masiva finalizada con errores</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"425\"/>\n        <source>Processed: </source>\n        <translation>Procesado: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"427\"/>\n        <source>Getting list of affected keys...</source>\n        <translation>Obteniendo lista de claves afectadas...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"475\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1097\"/>\n        <source>Success</source>\n        <translation>Correcto</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"501\"/>\n        <source>Confirmation</source>\n        <translation>Confirmación</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"502\"/>\n        <source>Do you really want to perform bulk operation?</source>\n        <translation>¿De verdad quieres realizar la operación masiva?</translation>\n    </message>\n    <message>\n        <source>Sign in with resp.app account</source>\n        <translation type=\"vanished\">Identificarse con una cuenta resp.app</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"231\"/>\n        <source>Renew your subscription</source>\n        <translation>Renueva tu suscripción</translation>\n    </message>\n    <message>\n        <source>Your trial has ended.</source>\n        <translation type=\"vanished\">Tu periodo de prueba ha finalizado.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"234\"/>\n        <source>You have no active subscription</source>\n        <translation>No tienes una suscripción activa</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"237\"/>\n        <source>No internet connection</source>\n        <translation>No hay conexión a Internet</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"240\"/>\n        <source>Your trial has ended</source>\n        <translation>Tu periodo de prueba ha finalizado</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"252\"/>\n        <source>To use this version you need to renew your subscription.</source>\n        <translation>Para usar esta version necesitas renovar tu suscripción.</translation>\n    </message>\n    <message>\n        <source>Please make sure that RDM is not blocked by a firewall and you have an internet connection.</source>\n        <translation type=\"vanished\">Asegúrate de que RDM no está bloqueado y tienes conexión a Internet</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"257\"/>\n        <source>If you’re behind a proxy please enable </source>\n        <translation>Actívalo si estás detrás de un Proxy</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"261\"/>\n        <source> option before sign-in.</source>\n        <translation> opción antes de iniciar sesión</translation>\n    </message>\n    <message>\n        <source>Please purchase a subscription to continue using RDM.</source>\n        <translation type=\"vanished\">Por favor compra una suscripción para seguir usando RDM.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"11\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"317\"/>\n        <source>Sign in with RESP.app account</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"255\"/>\n        <source>Please make sure that RESP.app is not blocked by a firewall and you have an internet connection.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"264\"/>\n        <source>Please purchase a subscription to continue using RESP.app.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"268\"/>\n        <source>If you have any questions please contact support </source>\n        <translation>Si tienes alguna pregunta por favor contacta con soporte </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"279\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Renew Subscription</source>\n        <translation>Renovar Suscripción</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"280\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"32\"/>\n        <source>Buy Subscription</source>\n        <translation>Comprar Suscripción</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"293\"/>\n        <source>Try Again</source>\n        <translation>Inténtalo de nuevo</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"331\"/>\n        <source>Email:</source>\n        <translation>Email:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"347\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"501\"/>\n        <source>Password:</source>\n        <translation>Contraseña:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"370\"/>\n        <location filename=\"../../qml/common/PasswordInput.qml\" line=\"29\"/>\n        <source>Show password</source>\n        <translation>Mostrar contraseña</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"376\"/>\n        <source>Forgot password?</source>\n        <translation>¿Contraseña olvidada?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"404\"/>\n        <source>Sign In</source>\n        <translation>Identificarse</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"409\"/>\n        <source>Please enter email &amp; password to sign in.</source>\n        <translation>Por favor introduce email &amp; contraseña para acceder.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"422\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"444\"/>\n        <source>Offline Activation</source>\n        <translation>Activación Offline</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"465\"/>\n        <source>Paste Activation code here</source>\n        <translation>Pegar aquí código de Activación</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"481\"/>\n        <source>Where can I find my activation code?</source>\n        <translation>¿Dónde puedo encontrar mi código de activación?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"490\"/>\n        <source>Activate</source>\n        <translation>Activar</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"495\"/>\n        <source>Please enter valid activation code.</source>\n        <translation>Por favor introduzca un código de activación válido</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/TreeItemDelegate.qml\" line=\"220\"/>\n        <source> (Removed)</source>\n        <translation> (Borrado)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"77\"/>\n        <source>Open Keys Filter</source>\n        <translation>Abrir Filtro de Claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"81\"/>\n        <source>Reload Keys in Database</source>\n        <translation>Recargar Claves en la Base de Datos</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"85\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"30\"/>\n        <source>Add New Key</source>\n        <translation>Añadir Nueva Clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Disable Live Update</source>\n        <translation>Desactivar Actualización Automática</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Enable Live Update</source>\n        <translation>Activar Actualización Automática</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"95\"/>\n        <source>Open Console</source>\n        <translation>Abrir Consola</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"98\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"32\"/>\n        <source>Analyze Used Memory</source>\n        <translation>Analizar Memoria Usada</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"100\"/>\n        <source>Bulk Operations</source>\n        <translation>Operaciones Masivas</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"121\"/>\n        <source>Flush Database</source>\n        <translation>Vaciar Base de Datos</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"124\"/>\n        <source>Delete keys with filter</source>\n        <translation>Borrar claves con filtro</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"97\"/>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"127\"/>\n        <source>Set TTL for multiple keys</source>\n        <translation>Asignar múltiples llaves TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"130\"/>\n        <source>Copy keys from this database to another</source>\n        <translation>Copiar claves desde esta base de datos a otra</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"133\"/>\n        <source>Import keys from RDB file</source>\n        <translation>Importar claves desde fichero RDB</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"136\"/>\n        <source>Back</source>\n        <translation>Atrás</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"22\"/>\n        <source>Copy Key Name</source>\n        <translation>Copiar Nombre de Clave</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"29\"/>\n        <source>Reload Namespace</source>\n        <translation>Recargar Namespace</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"31\"/>\n        <source>Copy Namespace Pattern</source>\n        <translation>Copiar Patrón de Namespace</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"33\"/>\n        <source>Delete Namespace</source>\n        <translation>Borrar Namespace</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"71\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"24\"/>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"14\"/>\n        <source>Disconnect</source>\n        <translation>Desconectar</translation>\n    </message>\n    <message>\n        <source>Server Info</source>\n        <translation type=\"vanished\">Información Servidor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"20\"/>\n        <source>Reload Server</source>\n        <translation>Recargar Servidor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"24\"/>\n        <source>Unload All Data</source>\n        <translation>Descargar Todos los Datos</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"28\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>Edit Connection Settings</source>\n        <translation>Editar Ajustes de Conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"32\"/>\n        <source>Duplicate Connection</source>\n        <translation>Duplicar Conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"36\"/>\n        <source>Delete Connection</source>\n        <translation>Borrar Conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"21\"/>\n        <source>Connecting...</source>\n        <translation>Conectando...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"193\"/>\n        <source>Clear</source>\n        <translation>Limpiar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"234\"/>\n        <source>Arguments</source>\n        <translation>Argumentos</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"240\"/>\n        <source>Description</source>\n        <translation>Descripción</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"246\"/>\n        <source>Available since</source>\n        <translation>Disponible desde</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"297\"/>\n        <source>Close</source>\n        <translation>Cerrar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"108\"/>\n        <source>View Server Info</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"130\"/>\n        <source>Redis Version</source>\n        <translation>Versión Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"169\"/>\n        <source>Used memory</source>\n        <translation>Memoria usada</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"182\"/>\n        <source>Cmd Processed</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"203\"/>\n        <source>Monitor Commands</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"242\"/>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"319\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"105\"/>\n        <source>Clients</source>\n        <translation>Clientes</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"377\"/>\n        <source>Server Actions</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Commands Processed</source>\n        <translation type=\"vanished\">Comandos Procesados</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"143\"/>\n        <source>Uptime</source>\n        <translation>Tiempo activo</translation>\n    </message>\n    <message>\n        <source>Total Keys</source>\n        <translation type=\"vanished\">Total de Claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"156\"/>\n        <source>Hit Ratio</source>\n        <translation>Ratio de Aciertos</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"262\"/>\n        <source>Server Stats</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"281\"/>\n        <source>Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"321\"/>\n        <source> day(s)</source>\n        <translation> día(s)</translation>\n    </message>\n    <message>\n        <source>Info</source>\n        <translation type=\"vanished\">Info</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"53\"/>\n        <source>Commands Per Second</source>\n        <translation>Comandos Por Segundo</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"67\"/>\n        <source>Ops/s</source>\n        <translation>Ops/s</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"91\"/>\n        <source>Connected Clients</source>\n        <translation>Clientes Conectados</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"129\"/>\n        <source>Memory Usage</source>\n        <translation>Uso de Memoria</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"141\"/>\n        <source>Mb</source>\n        <translation>Mb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"170\"/>\n        <source>Network Input</source>\n        <translation>Entrada de Red</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"182\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"218\"/>\n        <source>Kb/s</source>\n        <translation>Kb/s</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"206\"/>\n        <source>Network Output</source>\n        <translation>Salida de Red</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"242\"/>\n        <source>Total Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"256\"/>\n        <source>Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Keys</source>\n        <translation type=\"vanished\">Claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"36\"/>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"28\"/>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"39\"/>\n        <source>Auto Refresh</source>\n        <translation>Auto Refrescar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"66\"/>\n        <source>Property</source>\n        <translation>Propiedad</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"72\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"22\"/>\n        <source>Value</source>\n        <translation>Valor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"68\"/>\n        <source>Subscribe in Console</source>\n        <translation>Suscribirse en la consola</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"221\"/>\n        <source>Slowlog</source>\n        <translation>Slowlog</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"299\"/>\n        <source>Pub/Sub Channels</source>\n        <translation>Canales Pub/Sub</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"86\"/>\n        <source>Execution Time (μs)</source>\n        <translation>Tiempo de Ejecución (μs)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"38\"/>\n        <source>Enable</source>\n        <translation>Activar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"57\"/>\n        <source>Channel Name</source>\n        <translation>Nombre de Canal</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"54\"/>\n        <source>Command</source>\n        <translation>Comando</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"71\"/>\n        <source>Processed at</source>\n        <translation>Procesado en</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"51\"/>\n        <source>Client Address</source>\n        <translation>Dirección del Cliente</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"57\"/>\n        <source>Age (sec)</source>\n        <translation>Antigüedad (seg)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"63\"/>\n        <source>Idle</source>\n        <translation>Inactivo</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"69\"/>\n        <source>Flags</source>\n        <translation>Flags</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"75\"/>\n        <source>Current Database</source>\n        <translation>Base de Datos Actual</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"10\"/>\n        <source>Add New Key to </source>\n        <translation>Añadir Nueva Clave a </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"56\"/>\n        <location filename=\"../../qml/value-editor/editors/HashItemEditor.qml\" line=\"17\"/>\n        <source>Key:</source>\n        <translation>Clave:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"66\"/>\n        <source>Type:</source>\n        <translation>Tipo:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"107\"/>\n        <source>Or Import Value from the file</source>\n        <translation>O importar valor desde el fichero</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"113\"/>\n        <source>(Optional) Any file</source>\n        <translation>(Opcional) Cualquier fichero</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"115\"/>\n        <source>Select file with value</source>\n        <translation>Seleccionar fichero con valor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"39\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"127\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"254\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"616\"/>\n        <source>Save</source>\n        <translation>Guardar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Edit Connections Group</source>\n        <translation>Editar grupo de conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Add New Connections Group</source>\n        <translation>Agregar nuevo grupo de conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"29\"/>\n        <source>Group Name:</source>\n        <translation>Nombre del grupo:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1091\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/editors/formatters/ValueFormatters.qml\" line=\"251\"/>\n        <source>Error</source>\n        <translation>Error</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"12\"/>\n        <source>Page</source>\n        <translation>Página</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"68\"/>\n        <source>Enter valid value</source>\n        <translation>Introduzca un valor válido</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"286\"/>\n        <source>Formatting error</source>\n        <translation>Error de Formateo</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"291\"/>\n        <source>Unknown formatter error (Empty response)</source>\n        <translation>Error desconocido del formateador (Respuesta vacía)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"363\"/>\n        <source>[Binary]</source>\n        <translation>[Binario]</translation>\n    </message>\n    <message>\n        <source> [Compressed: </source>\n        <translation type=\"vanished\"> [Comprimido: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"544\"/>\n        <source>Copy to Clipboard</source>\n        <translation>Copiar al Portapapeles</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"600\"/>\n        <source>Exit </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"601\"/>\n        <source>Full Screen Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"617\"/>\n        <source>Save Changes</source>\n        <translation>Guardar Cambios</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"719\"/>\n        <source>Search string</source>\n        <translation>Buscar cadena</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find Next</source>\n        <translation>Encontrar Siguiente</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find</source>\n        <translation>Encontrar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"764\"/>\n        <source>Regex</source>\n        <translation>Regex</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"750\"/>\n        <source>Cannot find more results</source>\n        <translation>No se puede encontrar más resultados</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"400\"/>\n        <source>Try to decompress:</source>\n        <translation>Intento de descomprimir:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"401\"/>\n        <source>Decompressed:</source>\n        <translation>Descomprimido:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"491\"/>\n        <source>Cannot decompress value using </source>\n        <translation>No se puede descomprimir valor usando </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"751\"/>\n        <source>Cannot find any results</source>\n        <translation>No se puede encontrar ningún resultado</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"899\"/>\n        <source>Binary value is too large to display</source>\n        <translation>El valor binario es demasiado grande para mostrarse</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"366\"/>\n        <source>View as:</source>\n        <translation>Ver como:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"502\"/>\n        <source>Large value (&gt;150kB). Formatters are not available.</source>\n        <translation>Valor grande (&gt;150kB). Formateadores no disponibles.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"18\"/>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"30\"/>\n        <source>Score</source>\n        <translation>Score</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"194\"/>\n        <source>Path to dump.rdb file</source>\n        <translation>Ruta al fichero dump.rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"196\"/>\n        <source>Select dump.rdb</source>\n        <translation>Selecciona dump.rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"18\"/>\n        <source>ID</source>\n        <translation>ID</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"61\"/>\n        <source>Value (represented as JSON object)</source>\n        <translation>Valor (representado como objeto JSON)</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"127\"/>\n        <source>The row has been changed on server.Reload and try again.</source>\n        <translation>La fila ha cambiado en el servidor. Recarga e inténtalo de nuevo.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/bulkoperationsmanager.cpp\" line=\"131\"/>\n        <source>Failed to perform actions on %1 keys. </source>\n        <translation>Fallo al realizar acciones en las claves: %1. </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"12\"/>\n        <source>Cannot copy key </source>\n        <translation>No se puede copiar la clave </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"123\"/>\n        <source>Source connection error</source>\n        <translation>Error en la conexión de partida</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"135\"/>\n        <source>Target connection error</source>\n        <translation>Error en la conexión de destino</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/deleteoperation.cpp\" line=\"11\"/>\n        <source>Cannot remove key </source>\n        <translation>No se puede borrar la clave </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"17\"/>\n        <source>Cannot execute command </source>\n        <translation>No se puede ejecutar el comando </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"26\"/>\n        <source>Invalid regexp for keys filter.</source>\n        <translation>Regexp inválido para filtro de claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"39\"/>\n        <source>Cannot get the list of affected keys</source>\n        <translation>No se puede obtener la lista de claves afectadas</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/ttloperation.cpp\" line=\"11\"/>\n        <source>Cannot set TTL for key </source>\n        <translation>No se puede asignar el TTL a la clave </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/abstractnamespaceitem.cpp\" line=\"381\"/>\n        <source>Your redis-server doesn&apos;t support &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; commands.</source>\n        <translation>El servidor redis no soporta comandos &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt;.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"138\"/>\n        <source>Key was added. Do you want to reload keys in selected namespace?</source>\n        <translation>Clave añadida. ¿Quiere recargar las claves en el namespace seleccionado?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"147\"/>\n        <source>Network is not accessible. Please ensure that you have internet access and try again.</source>\n        <translation>Red no accesible. Por favor comprueba que tienes acceso a Internet y prueba otra vez.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"190\"/>\n        <source>Invalid login or password</source>\n        <translation>Login o password inválidos</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"200\"/>\n        <source>Too many requests from your IP</source>\n        <translation>Demasiadas peticiones desde tu IP</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"206\"/>\n        <source>Unknown error. Status code %1</source>\n        <translation>Error desconocido. Código estado %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"321\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"734\"/>\n        <source>Cannot parse server reply</source>\n        <translation>No se puede interpretar la respuesta del servidor</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"341\"/>\n        <source>Cannot validate token</source>\n        <translation>No se puede validar token</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"347\"/>\n        <source>Cannot login - %1. &lt;br/&gt; Please try again or contact  &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</source>\n        <translation type=\"unfinished\">No se puede iniciar sesión - %1. &lt;br/&gt; Por favor, inténtalo de nuevo o contacta con &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"718\"/>\n        <source>Expired activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"731\"/>\n        <source>Invalid activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"588\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"614\"/>\n        <source>Cannot save the update. Disk is full or download folder is not writable.</source>\n        <translation>No se puede guardar la actualización. Disco lleno o no se puede escribir en la carpeta.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"666\"/>\n        <source>Download was canceled</source>\n        <translation>Se canceló la descarga</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"673\"/>\n        <source>Network error</source>\n        <translation>Error de red</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/FilePathInput.qml\" line=\"27\"/>\n        <source>Select File</source>\n        <translation>Seleccionar Fichero</translation>\n    </message>\n    <message>\n        <source>Save to File</source>\n        <translation type=\"vanished\">Guardar a Fichero</translation>\n    </message>\n    <message>\n        <source>Save Value</source>\n        <translation type=\"vanished\">Guardar Valor</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"913\"/>\n        <source>Save value to file</source>\n        <translation>Guardar valor a fichero</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Raw Value to File</source>\n        <translation>Guardar Valor en Bruto en Fichero</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Formatted Value to File</source>\n        <translation>Guardar Valor Formateado en Fichero</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Raw Value</source>\n        <translation>Guardar Valor en Bruto</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Formatted Value</source>\n        <translation>Guardar Valor Formateado</translation>\n    </message>\n    <message>\n        <source>Save raw value to file</source>\n        <translation type=\"vanished\">Guardar Valor Formateado en Fichero</translation>\n    </message>\n    <message>\n        <source>Save formatted value to file</source>\n        <translation type=\"vanished\">Guardar Valor Formateado en Fichero</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"51\"/>\n        <source>Value was saved to file:</source>\n        <translation>Valor guardado en fichero:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/abstractoperation.cpp\" line=\"38\"/>\n        <source>Cannot connect to redis-server</source>\n        <translation>No se puede conectar al servidor Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"13\"/>\n        <source>Edit Connection Group</source>\n        <translation>Editar grupo de conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"17\"/>\n        <source>Delete Connection Group</source>\n        <translation>Borrar grupo de conexión</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/servergroup.cpp\" line=\"58\"/>\n        <source>Do you really want to delete group &lt;b&gt;with all connections&lt;/b&gt;?</source>\n        <translation>¿Realmente deseas borrar el grupo &lt;b&gt;con todas sus conexiones&lt;/b&gt;?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"8\"/>\n        <source>Order of elements:</source>\n        <translation>Órden de elementos:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"20\"/>\n        <source>Default</source>\n        <translation>Por defecto</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"21\"/>\n        <source>Reverse</source>\n        <translation>Invertir</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"28\"/>\n        <source>Start date should be less than End date</source>\n        <translation>Fecha de inicio debe ser menor a la fecha de fin</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"136\"/>\n        <source>Apply filter</source>\n        <translation>Aplicar filtro</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"30\"/>\n        <source>&lt;span style=&quot;font-size: 11px;&quot;&gt;Powered by awesome &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;open-source software&lt;/a&gt; and &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</source>\n        <translation>&lt;span style=&quot;font-size: 11px;&quot;&gt;Impulsado por asombroso &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;software open-source&lt;/a&gt; y &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"19\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"25\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"49\"/>\n        <source>Trial is active till</source>\n        <translation>Prueba activa hasta</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"58\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"59\"/>\n        <source>Licensed to</source>\n        <translation>Licenciado a</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"66\"/>\n        <source>Subscription is active until:</source>\n        <translation>Suscripción activa hasta:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Manage Subscription</source>\n        <translation>Administrar Suscripción</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"11\"/>\n        <source>Getting Started</source>\n        <translation>Empezar</translation>\n    </message>\n    <message>\n        <source>Thank you for choosing RDM. Let&apos;s make your Redis experience better.</source>\n        <translation type=\"vanished\">Gracias por elegir RDM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"42\"/>\n        <source>Thank you for choosing RESP.app. Let&apos;s make your Redis experience better.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"60\"/>\n        <source>Connect to Redis-Server</source>\n        <translation>Conectar a Servidor Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"73\"/>\n        <source>Read the Docs</source>\n        <translation>Leer la Documentación</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/loadmoreitem.cpp\" line=\"12\"/>\n        <source>Load more keys</source>\n        <translation>Cargar más claves</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"24\"/>\n        <source>Yes</source>\n        <translation>Sí</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"32\"/>\n        <source>No</source>\n        <translation>No</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"19\"/>\n        <source>SSH Passphrase</source>\n        <translation>Contraseña SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"21\"/>\n        <source>Unknown</source>\n        <translation>Desconocido</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <source>Passphrase</source>\n        <translation>Contraseña</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"71\"/>\n        <source>Continue</source>\n        <translation>Continuar</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/ColorInput.qml\" line=\"43\"/>\n        <source>Select</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/UnsupportedDataType.qml\" line=\"24\"/>\n        <source>Unsupported Redis Data type </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"163\"/>\n        <source>Cannot delete key:\n\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "src/resources/translations/rdm_ja_JP.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"ja_JP\">\n<context>\n    <name>QObject</name>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"360\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"397\"/>\n        <source>Cannot connect to cluster node %1:%2</source>\n        <translation>クラスタノードに書き込みできません&#x3000;%1:%2</translation>\n    </message>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"408\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"435\"/>\n        <source>Cannot flush db (%1): %2</source>\n        <translation>DBをフラッシュできません&#x3000;(%1): %2</translation>\n    </message>\n</context>\n<context>\n    <name>RESP</name>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"82\"/>\n        <source>Settings directory is not writable</source>\n        <translation>設定したディレクトリは書き込みできません</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"84\"/>\n        <source>RESP.app can&apos;t save connections file to settings directory. Please change file permissions or restart RESP.app as administrator.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"160\"/>\n        <source>Cannot parse scan response</source>\n        <translation>スキャンのレスポンスをパースできません</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"328\"/>\n        <source>Server returned unexpected response: </source>\n        <translation>サーバが不正なレスポンスを返しました:</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"106\"/>\n        <source>Cannot set TTL for key %1</source>\n        <translation>キー%1にTTLを設定できません</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"81\"/>\n        <source>Cannot rename key %1: %2</source>\n        <translation>キー%1を変更できません: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"125\"/>\n        <source>Cannot persist key &apos;%1&apos;. &lt;br&gt; Key does not exist or does not have an assigned TTL value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"274\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"285\"/>\n        <source>Cannot load rows for key %1: %2</source>\n        <translation>キー%1のROWを読むことができません: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"42\"/>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"75\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"14\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"41\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"12\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"33\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"44\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"77\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"48\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"59\"/>\n        <source>Invalid row</source>\n        <translation>不正なROWです</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"113\"/>\n        <source>Value with the same key already exists</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"184\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"340\"/>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"151\"/>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"84\"/>\n        <source>Connection error: </source>\n        <translation>接続エラー: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"136\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"136\"/>\n        <source>Data was loaded from server partially.</source>\n        <translation>サーバからデータが部分的にロードされました。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"26\"/>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"38\"/>\n        <source>Cannot load key %1, connection error occurred: %2</source>\n        <translation>キー%1を読めません。接続エラーが発生しました: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"49\"/>\n        <source>Cannot load key %1 because it doesn&apos;t exist in database. Please reload connection tree and try again.</source>\n        <translation>データベースに存在しないためキー%1を読めません。接続ツリーをリロードしてから改めて試してください。</translation>\n    </message>\n    <message>\n        <source>Cannot load TTL for key %1, connection error occurred: %2</source>\n        <translation type=\"vanished\">キー%1のTTLがロードできません。接続エラーが発生しました: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"78\"/>\n        <source>Cannot retrieve type of the key: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"122\"/>\n        <source>Cannot open file with key value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"129\"/>\n        <source>Cannot connect to server &apos;%1&apos;. Check log for details.</source>\n        <translation>サーバ&apos;%1&apos;に接続できません。詳細はログを確認してください。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"139\"/>\n        <source>Open Source version of RESP.app &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"229\"/>\n        <source>Cannot load keys: %1</source>\n        <translation>キーをロードできません: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"336\"/>\n        <source>Delete key error: %1</source>\n        <translation>キー削除エラー: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"477\"/>\n        <source>Cannot determine amount of used memory by key: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"416\"/>\n        <source>Cannot flush database: </source>\n        <translation>データベースをフラッシュできません: </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/common/tabmodel.cpp\" line=\"43\"/>\n        <source>Invalid Connection. Check connection settings.</source>\n        <translation>不正な接続です。接続の設定を確認してください。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"251\"/>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carefully or change limit in settings.</source>\n        <translation>キーの上限を超えているためライブアップデートは無効です。より適切なフィルタを指定するか、または設定で上限を変更してください。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"304\"/>\n        <source>Key was added. Do you want to reload keys in selected database?</source>\n        <translation>キーを追加しました。選択したデータベースのキーをリロードしますか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"312\"/>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"143\"/>\n        <source>Key was added</source>\n        <translation>キーを追加しました。</translation>\n    </message>\n    <message>\n        <source>Another operation is currently in progress</source>\n        <translation type=\"vanished\">別の処理が動作しています。</translation>\n    </message>\n    <message>\n        <source>Please wait until another operation will be finished.</source>\n        <translation type=\"vanished\">処理が終わるまでお待ちください。</translation>\n    </message>\n    <message>\n        <source>Please wait until another operation will be finised.</source>\n        <translation type=\"vanished\">処理が終わるまでお待ちください。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"327\"/>\n        <source>Do you really want to remove all keys from this database?</source>\n        <translation>このデータベースから全てのキーを削除しても本当によろしいですか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"73\"/>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"75\"/>\n        <source>Cannot load databases:\n\n</source>\n        <translation>データベースをロードできません:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"250\"/>\n        <source>Live update was disabled</source>\n        <translation>ライブ・アップデートは無効です</translation>\n    </message>\n    <message>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carrfully or change limit in settings.</source>\n        <translation type=\"vanished\">キーの上限を超えているためライブアップデートは無効です。より適切なフィルタを指定するか、または設定で上限を変更してください。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"190\"/>\n        <source>Rename key</source>\n        <translation>キー名の変更</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"199\"/>\n        <source>New name:</source>\n        <translation>新しい名前:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"21\"/>\n        <source>Total pages: </source>\n        <translation>総ページ数: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"45\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"222\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"358\"/>\n        <source>Size: </source>\n        <translation>サイズ: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"228\"/>\n        <source>TTL:</source>\n        <translation>TTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"233\"/>\n        <source>Set key TTL</source>\n        <translation>キーのTTLを設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"242\"/>\n        <source>New TTL:</source>\n        <translation>新しいTTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"298\"/>\n        <source>Delete</source>\n        <translation>削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"23\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"303\"/>\n        <source>Delete key</source>\n        <translation>キーを削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"38\"/>\n        <source>Changes are not saved</source>\n        <translation>変更は保存されていません</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"39\"/>\n        <source>Do you want to close key tab without saving changes?</source>\n        <translation>変更を保存せずにキーのタブを閉じますか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"260\"/>\n        <source>Persist key</source>\n        <translation>永続化キー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"304\"/>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"153\"/>\n        <source>Do you really want to delete this key?</source>\n        <translation>このキーを本当に削除してもよろしいですか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"140\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"318\"/>\n        <source>Reload Value</source>\n        <translation>値をリロード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"22\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"31\"/>\n        <source>Add Row</source>\n        <translation>ROWを追加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"30\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"526\"/>\n        <source>Add Element to HLL</source>\n        <translation>HLLに要素を追加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"68\"/>\n        <source>Add</source>\n        <translation>追加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"101\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"122\"/>\n        <source>Delete row</source>\n        <translation>ROWを削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"107\"/>\n        <source>The row is the last one in the key. After removing it key will be deleted.</source>\n        <translation>このROWはこのキーの最後の1つです。削除後はキーも削除されます。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"109\"/>\n        <source>Do you really want to remove this row?</source>\n        <translation>このROWを本当に削除しますか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"164\"/>\n        <source>Search on page...</source>\n        <translation>ページを検索...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"191\"/>\n        <source>Full Search</source>\n        <translation>すべて検索</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"191\"/>\n        <source>Value and Console tabs related to this connection will be closed. Do you want to continue?</source>\n        <translation>この接続に関連する値とコンソールのタブを閉じます。続行しますか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"204\"/>\n        <source>Do you really want to delete connection?</source>\n        <translation>接続を本当に削除しますか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"13\"/>\n        <source>Connected to cluster.\n</source>\n        <translation>クラスタに接続しました。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"16\"/>\n        <source>Connected.\n</source>\n        <translation>接続しました。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"60\"/>\n        <source>Switch to %1 mode. Close console tab to stop listen for messages.</source>\n        <translation>%1モードに変更する。メッセージを見るのをやめるにはコンソールのタブを閉じてください。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"69\"/>\n        <source>Subscribe error: %1</source>\n        <translation>サブスクライブエラー: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/server-actions/serverstatsmodel.cpp\" line=\"36\"/>\n        <source>Server %0</source>\n        <translation>サーバ %0</translation>\n    </message>\n    <message>\n        <source>Can&apos;t find formatter with name: %1</source>\n        <translation type=\"vanished\">指定したフォーマッタが見つかりません: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"109\"/>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"147\"/>\n        <source>Can&apos;t find formatter: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"115\"/>\n        <source>Invalid callback</source>\n        <translation>不正なコールバックです</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"204\"/>\n        <source>Can&apos;t load list of available formatters from extension server: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"260\"/>\n        <source>Can&apos;t encode value: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Cannot decode value using %1 formatter. </source>\n        <translation type=\"vanished\">フォーマッタ%1で値をデコードすることができません。</translation>\n    </message>\n    <message>\n        <source>Cannot validate value using %1 formatter.</source>\n        <translation type=\"vanished\">フォーマッタ%1で値を検証することができません。</translation>\n    </message>\n    <message>\n        <source>Cannot encode value using %1 formatter. </source>\n        <translation type=\"vanished\">フォーマッタ%1で値をエンコードすることができません。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"26\"/>\n        <source>Loading key: %1 from db %2</source>\n        <translation>データをロード: DB %2 の %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"68\"/>\n        <source>Cannot open value tab</source>\n        <translation>値タブを開くことができません</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"97\"/>\n        <source>Connection error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"115\"/>\n        <source>Connection error. Can&apos;t open value tab. </source>\n        <translation>接続エラー。値タブを開けません。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"176\"/>\n        <source>Cannot reload key value: %1</source>\n        <translation>キーの値をリロードできません: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"228\"/>\n        <source>Cannot load key value: %1</source>\n        <translation>キーの値をロードできません: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"29\"/>\n        <source>Connect to Redis Server</source>\n        <translation>Redisサーバに接続</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"117\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"205\"/>\n        <source>Import</source>\n        <translation>インポート</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"50\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"69\"/>\n        <source>Import Connections</source>\n        <translation>接続情報のインポート</translation>\n    </message>\n    <message>\n        <source>Export</source>\n        <translation type=\"vanished\">エクスポート</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"58\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"74\"/>\n        <source>Export Connections</source>\n        <translation>接続情報のエクスポート</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"100\"/>\n        <source>Report issue</source>\n        <translation>問題を報告</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"107\"/>\n        <source>Documentation</source>\n        <translation>ドキュメント</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"114\"/>\n        <source>Join Telegram Chat</source>\n        <translation>Telegram Chatに参加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"121\"/>\n        <source>Follow</source>\n        <translation>フォローする</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"128\"/>\n        <source>Star on GitHub!</source>\n        <translation>GitHubで貢献する!</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"136\"/>\n        <source>Log</source>\n        <translation>ログ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"144\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"13\"/>\n        <source>Extension Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"154\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"13\"/>\n        <source>Settings</source>\n        <translation>設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>New Connection Settings</source>\n        <translation>新しい接続の設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"144\"/>\n        <source>How to connect</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"151\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"48\"/>\n        <source>Connection Settings</source>\n        <translation>接続の設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"180\"/>\n        <source>Create connection from Redis URL</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"233\"/>\n        <source>Learn more about Redis URL:  </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"240\"/>\n        <source>Connection guides</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"260\"/>\n        <source>Local or Public Redis</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"276\"/>\n        <source>Redis with SSL/TLS</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"292\"/>\n        <source>SSH tunnel</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"308\"/>\n        <source>UNIX socket</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"395\"/>\n        <source>Cannot figure out how to connect to your redis-server?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"401\"/>\n        <source>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Read the Docs&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;Contact Support&lt;/a&gt; or ask for help in our &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram Group&lt;/a&gt;</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"413\"/>\n        <source>Don&apos;t have running Redis?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"419\"/>\n        <source>Spin up hassle-free Redis on Digital Ocean</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"431\"/>\n        <source>Skip</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"466\"/>\n        <source>Name:</source>\n        <translation>名前:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"472\"/>\n        <source>Connection Name</source>\n        <translation>接続名</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"478\"/>\n        <source>Address:</source>\n        <translation>アドレス:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"483\"/>\n        <source>redis-server host</source>\n        <translation>Redisサーバのホスト</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"494\"/>\n        <source>For better network performance please use 127.0.0.1</source>\n        <translation>127.0.0.1を使うとネットワークのパフォーマンスが向上します</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"507\"/>\n        <source>(Optional) redis-server authentication password</source>\n        <translation>(任意) Redisサーバ認証パスワード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"512\"/>\n        <source>Username:</source>\n        <translation>ユーザー名:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"518\"/>\n        <source>(Optional) redis-server authentication username (Redis &gt;6.0)</source>\n        <translation>(任意) Redisサーバ認証ユーザー名 (Redis &gt;6.0)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"526\"/>\n        <source>Security</source>\n        <translation>セキュリティ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"573\"/>\n        <source>Public Key:</source>\n        <translation>公開鍵:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"579\"/>\n        <source>(Optional) Public Key in PEM format</source>\n        <translation>(任意) PEM形式の公開鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"581\"/>\n        <source>Select public key in PEM format</source>\n        <translation>PEM形式の公開鍵を選択してください</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"592\"/>\n        <source>(Optional) Private Key in PEM format</source>\n        <translation>(任意) PEM形式の非公開鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"594\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"703\"/>\n        <source>Select private key in PEM format</source>\n        <translation>PEM形式の非公開鍵を選択してください</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"599\"/>\n        <source>Authority:</source>\n        <translation>証明書:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"605\"/>\n        <source>(Optional) Authority in PEM format</source>\n        <translation>(任意) PEM形式の証明書</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"607\"/>\n        <source>Select authority file in PEM format</source>\n        <translation>PEM形式の証明書を選択してください</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"553\"/>\n        <source>SSH Tunnel</source>\n        <translation>SSHトンネル</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"630\"/>\n        <source>SSH Address:</source>\n        <translation>SSHアドレス</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"634\"/>\n        <source>Remote Host with SSH server</source>\n        <translation>SSHサーバのリモートホスト</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"642\"/>\n        <source>SSH User:</source>\n        <translation>SSHユーザー:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"648\"/>\n        <source>Valid SSH User Name</source>\n        <translation>有効なSSHユーザー名</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"586\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"683\"/>\n        <source>Private Key</source>\n        <translation>非公開鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"701\"/>\n        <source>Path to Private Key in PEM format</source>\n        <translation>PEM形式の非公開鍵のファイルパス</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"711\"/>\n        <source>&lt;b&gt;Tip:&lt;/b&gt; Use &lt;code&gt;⌘ + Shift + .&lt;/code&gt; to show hidden files and folders in dialog</source>\n        <translation>&lt;b&gt;Tip:&lt;/b&gt; &lt;code&gt;⌘ + Shift + .&lt;/code&gt;で隠しファイルや隠しフォルダをダイアログに表示できます</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"87\"/>\n        <source>Password</source>\n        <translation>パスワード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"737\"/>\n        <source>SSH User Password</source>\n        <translation>SSHユーザーのパスワード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"759\"/>\n        <source>Enable TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</source>\n        <translation>TLS-over-SSHを有効にする。(&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"156\"/>\n        <source>Advanced Settings</source>\n        <translation>詳細設定</translation>\n    </message>\n    <message>\n        <source>Sign in with resp.app account</source>\n        <translation type=\"vanished\">resp.appのアカウントでサインイン</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"11\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"317\"/>\n        <source>Sign in with RESP.app account</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"231\"/>\n        <source>Renew your subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"234\"/>\n        <source>You have no active subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"237\"/>\n        <source>No internet connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"240\"/>\n        <source>Your trial has ended</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"252\"/>\n        <source>To use this version you need to renew your subscription.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"255\"/>\n        <source>Please make sure that RESP.app is not blocked by a firewall and you have an internet connection.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"257\"/>\n        <source>If you’re behind a proxy please enable </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"261\"/>\n        <source> option before sign-in.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"264\"/>\n        <source>Please purchase a subscription to continue using RESP.app.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"268\"/>\n        <source>If you have any questions please contact support </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"279\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Renew Subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"280\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"32\"/>\n        <source>Buy Subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"293\"/>\n        <source>Try Again</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"331\"/>\n        <source>Email:</source>\n        <translation>Email:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"347\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"501\"/>\n        <source>Password:</source>\n        <translation>パスワード:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"376\"/>\n        <source>Forgot password?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"393\"/>\n        <source>Application will be restarted to apply this setting.</source>\n        <translation>設定を有効にするためアプリケーションは再起動されます</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"404\"/>\n        <source>Sign In</source>\n        <translation>サインイン</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"409\"/>\n        <source>Please enter email &amp; password to sign in.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"422\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"444\"/>\n        <source>Offline Activation</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"465\"/>\n        <source>Paste Activation code here</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"481\"/>\n        <source>Where can I find my activation code?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"490\"/>\n        <source>Activate</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"495\"/>\n        <source>Please enter valid activation code.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"540\"/>\n        <source>SSL / TLS</source>\n        <translation>SSL / TLS</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"612\"/>\n        <source>Enable strict mode:</source>\n        <translation>ストリクトモードを有効にする</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"657\"/>\n        <source>Use SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"667\"/>\n        <source>(Optional) Custom SSH Agent Path</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"669\"/>\n        <source>Select SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"678\"/>\n        <source>Additional configuration is required to enable SSH Agent support</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"734\"/>\n        <source>Passphrase for provided private key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"736\"/>\n        <source>Password request will be prompt prior to connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"747\"/>\n        <source>Ask for password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"796\"/>\n        <source>Keys loading</source>\n        <translation>キーの読み込み</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"800\"/>\n        <source>Default filter:</source>\n        <translation>規定のフィルタ:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"806\"/>\n        <source>Pattern which defines loaded keys from redis-server</source>\n        <translation>Redisサーバからロードするキー定義のパターン</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"812\"/>\n        <source>Namespace Separator:</source>\n        <translation>ネームスペースのセパレータ:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"819\"/>\n        <source>Separator used for namespace extraction from keys</source>\n        <translation>キーから抽出するネームスペースに使用するセパレータ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"825\"/>\n        <source>Timeouts &amp; Limits</source>\n        <translation>タイムアウトと上限</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"829\"/>\n        <source>Connection Timeout (sec):</source>\n        <translation>接続タイムアウト(秒)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"842\"/>\n        <source>Execution Timeout (sec):</source>\n        <translation>実行タイムアウト(秒)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"853\"/>\n        <source>Databases discovery limit:</source>\n        <translation>データベース探索リミット:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"867\"/>\n        <source>Cluster</source>\n        <translation>クラスタ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"871\"/>\n        <source>Change host on cluster redirects:</source>\n        <translation>クラスタをリダイレクトするホストを変更:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"881\"/>\n        <source>Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"885\"/>\n        <source>Default value formatter:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"901\"/>\n        <source>Auto detect (JSON / Plain Text / HEX)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"902\"/>\n        <source>Last selected</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"903\"/>\n        <source>Select formatter ...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"952\"/>\n        <source>Appearance</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"956\"/>\n        <source>Icon color:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1021\"/>\n        <source>Invalid settings detected!</source>\n        <translation>不正な設定を検出しました!</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"992\"/>\n        <source>Test Connection</source>\n        <translation>接続テスト</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/OkDialogOverlay.qml\" line=\"20\"/>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"111\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1029\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"163\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"319\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"903\"/>\n        <source>OK</source>\n        <translation>OK</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"294\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"508\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"402\"/>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"44\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"61\"/>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"89\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1057\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"175\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"331\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"172\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"89\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"268\"/>\n        <source>Cancel</source>\n        <translation>キャンセル</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"48\"/>\n        <source>General</source>\n        <translation>一般</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"53\"/>\n        <source>Application will be restarted to apply these settings.</source>\n        <translation>アプリケーションを再起動すると設定が有効になります。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"73\"/>\n        <source>Language</source>\n        <translation>言語</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"85\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"174\"/>\n        <source>Font</source>\n        <translation>フォント</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"97\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"186\"/>\n        <source>Font Size</source>\n        <translation>フォントサイズ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"110\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"124\"/>\n        <source>Dark Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"200\"/>\n        <source>Maximum Formatted Value Size</source>\n        <translation>フォーマット済み値の最大サイズ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"201\"/>\n        <source>Size in bytes</source>\n        <translation>バイト数</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"259\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"392\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"138\"/>\n        <source>Use system proxy settings</source>\n        <translation>OSのプロキシ設定を使う</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"150\"/>\n        <source>Use system proxy only for HTTP(S) requests</source>\n        <translation>HTTP(S)にシステムのプロキシ設定のみを使う</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"156\"/>\n        <source>Value Editor</source>\n        <translation>値エディタ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"213\"/>\n        <source>Maximum amount of items per page</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"218\"/>\n        <source>Connections Tree</source>\n        <translation>接続ツリー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"236\"/>\n        <source>Show namespaced keys on top</source>\n        <translation>ネームスペース付きのキーを上に表示</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"246\"/>\n        <source>Reopen namespaces on reload</source>\n        <translation>リロード時にネームスペースを開きなおす</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"247\"/>\n        <source>(Disable to improve treeview performance)</source>\n        <translation>(無効にするとツリービューが早くなります)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"258\"/>\n        <source>Show only last part for namespaced keys</source>\n        <translation>ネームスペース付きのキーの末尾のみを表示</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"270\"/>\n        <source>Limit for SCAN command</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"282\"/>\n        <source>Maximum amount of rendered child items</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"294\"/>\n        <source>Live update maximum allowed keys</source>\n        <translation>ライブアップデートで読み込むキーの最大数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"306\"/>\n        <source>Live update interval (in seconds)</source>\n        <translation>ライブアップデートの更新頻度(秒)</translation>\n    </message>\n    <message>\n        <source>External Value View Formatters</source>\n        <translation type=\"vanished\">外部の値ビューフォーマッタ</translation>\n    </message>\n    <message>\n        <source>Formatters path: %0</source>\n        <translation type=\"vanished\">フォーマッタのパス: %0</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"61\"/>\n        <source>Server Url:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"75\"/>\n        <source>Basic Auth:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"81\"/>\n        <source>User</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"99\"/>\n        <source>Response timeout  (in seconds)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"109\"/>\n        <source>Available Data Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"117\"/>\n        <source>Reload</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"135\"/>\n        <source>Id</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"141\"/>\n        <source>Name</source>\n        <translation>名称</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"147\"/>\n        <source>Read Only</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"29\"/>\n        <source>Version</source>\n        <translation>バージョン</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1001\"/>\n        <source>Quick Start Guide</source>\n        <translation>クイックスタート・ガイド</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"134\"/>\n        <source>Successful connection to redis-server</source>\n        <translation>Redisサーバへの接続に成功</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"137\"/>\n        <source>Can&apos;t connect to redis-server</source>\n        <translation>Redisサーバに接続できません</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"321\"/>\n        <source>Add Group</source>\n        <translation>グループを追加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"336\"/>\n        <source>Regroup connections</source>\n        <translation>接続グループの編集</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"358\"/>\n        <source>Exit Regroup Mode</source>\n        <translation>接続グループ編集モードを終了</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"370\"/>\n        <location filename=\"../../qml/common/PasswordInput.qml\" line=\"29\"/>\n        <source>Show password</source>\n        <translation>パスワードを表示</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/TreeItemDelegate.qml\" line=\"220\"/>\n        <source> (Removed)</source>\n        <translation> (削除済)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"77\"/>\n        <source>Open Keys Filter</source>\n        <translation>キーフィルタを開く</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"81\"/>\n        <source>Reload Keys in Database</source>\n        <translation>データベースのキーをリロード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"85\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"30\"/>\n        <source>Add New Key</source>\n        <translation>キーを追加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Disable Live Update</source>\n        <translation>ライブアップデートを無効化</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Enable Live Update</source>\n        <translation>ライブアップデートを有効化</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"95\"/>\n        <source>Open Console</source>\n        <translation>コンソールを開く</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"98\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"32\"/>\n        <source>Analyze Used Memory</source>\n        <translation>メモリを分析</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"100\"/>\n        <source>Bulk Operations</source>\n        <translation>バッチ処理</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"121\"/>\n        <source>Flush Database</source>\n        <translation>データベースを初期化</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"124\"/>\n        <source>Delete keys with filter</source>\n        <translation>フィルタを用いてキーを削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"97\"/>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"127\"/>\n        <source>Set TTL for multiple keys</source>\n        <translation>複数のキーにTTLを設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"130\"/>\n        <source>Copy keys from this database to another</source>\n        <translation>このデータベースから別のデータベースへキーをコピー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"133\"/>\n        <source>Import keys from RDB file</source>\n        <translation>RDBファイルからキーをインポート</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"136\"/>\n        <source>Back</source>\n        <translation>戻る</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"22\"/>\n        <source>Copy Key Name</source>\n        <translation>キー名をコピー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"29\"/>\n        <source>Reload Namespace</source>\n        <translation>ネームスペースをリロード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"31\"/>\n        <source>Copy Namespace Pattern</source>\n        <translation>ネームスペースのパターンをコピー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"33\"/>\n        <source>Delete Namespace</source>\n        <translation>ネームスペースを削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"71\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"24\"/>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"14\"/>\n        <source>Disconnect</source>\n        <translation>接続終了</translation>\n    </message>\n    <message>\n        <source>Server Info</source>\n        <translation type=\"vanished\">サーバ情報</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"20\"/>\n        <source>Reload Server</source>\n        <translation>サーバをリロード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"24\"/>\n        <source>Unload All Data</source>\n        <translation>全てのデータをアンロード</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"28\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>Edit Connection Settings</source>\n        <translation>接続情報を編集</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"32\"/>\n        <source>Duplicate Connection</source>\n        <translation>接続情報をコピー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"36\"/>\n        <source>Delete Connection</source>\n        <translation>接続情報を削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"21\"/>\n        <source>Connecting...</source>\n        <translation>接続中...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"193\"/>\n        <source>Clear</source>\n        <translation>消去</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"234\"/>\n        <source>Arguments</source>\n        <translation>パラメーター</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"240\"/>\n        <source>Description</source>\n        <translation>説明</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"246\"/>\n        <source>Available since</source>\n        <translation>利用可能</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"297\"/>\n        <source>Close</source>\n        <translation>閉じる</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"108\"/>\n        <source>View Server Info</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"130\"/>\n        <source>Redis Version</source>\n        <translation>Redisバージョン</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"169\"/>\n        <source>Used memory</source>\n        <translation>消費メモリ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"182\"/>\n        <source>Cmd Processed</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"203\"/>\n        <source>Monitor Commands</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"242\"/>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"319\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"105\"/>\n        <source>Clients</source>\n        <translation>クライアント数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"377\"/>\n        <source>Server Actions</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Commands Processed</source>\n        <translation type=\"vanished\">実行コマンド数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"143\"/>\n        <source>Uptime</source>\n        <translation>稼働時間</translation>\n    </message>\n    <message>\n        <source>Total Keys</source>\n        <translation type=\"vanished\">総キー数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"156\"/>\n        <source>Hit Ratio</source>\n        <translation>ヒット率</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"262\"/>\n        <source>Server Stats</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"281\"/>\n        <source>Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"321\"/>\n        <source> day(s)</source>\n        <translation> 日</translation>\n    </message>\n    <message>\n        <source>Info</source>\n        <translation type=\"vanished\">情報</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"53\"/>\n        <source>Commands Per Second</source>\n        <translation>コマンド数/秒</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"67\"/>\n        <source>Ops/s</source>\n        <translation>OP数/秒</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"91\"/>\n        <source>Connected Clients</source>\n        <translation>クライアント接続数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"129\"/>\n        <source>Memory Usage</source>\n        <translation>メモリ消費量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"141\"/>\n        <source>Mb</source>\n        <translation>MB</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"170\"/>\n        <source>Network Input</source>\n        <translation>ネットワーク入力</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"182\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"218\"/>\n        <source>Kb/s</source>\n        <translation>KB/秒</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"206\"/>\n        <source>Network Output</source>\n        <translation>ネットワーク出力</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"242\"/>\n        <source>Total Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"256\"/>\n        <source>Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Keys</source>\n        <translation type=\"vanished\">キー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"36\"/>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"28\"/>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"39\"/>\n        <source>Auto Refresh</source>\n        <translation>自動リフレッシュ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"66\"/>\n        <source>Property</source>\n        <translation>プロパティ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"72\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"22\"/>\n        <source>Value</source>\n        <translation>値</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"68\"/>\n        <source>Subscribe in Console</source>\n        <translation>コンソール上でサブスクライブ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"221\"/>\n        <source>Slowlog</source>\n        <translation>Slowlog</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"299\"/>\n        <source>Pub/Sub Channels</source>\n        <translation>Pub/Subチャンネル</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"38\"/>\n        <source>Enable</source>\n        <translation>有効</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"57\"/>\n        <source>Channel Name</source>\n        <translation>チャンネル名</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"54\"/>\n        <source>Command</source>\n        <translation>コマンド</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"71\"/>\n        <source>Processed at</source>\n        <translation>処理</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"86\"/>\n        <source>Execution Time (μs)</source>\n        <translation>実行時間(μs)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"51\"/>\n        <source>Client Address</source>\n        <translation>クライアントのアドレス</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"57\"/>\n        <source>Age (sec)</source>\n        <translation>経過時間(秒)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"63\"/>\n        <source>Idle</source>\n        <translation>アイドル</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"69\"/>\n        <source>Flags</source>\n        <translation>フラグ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"75\"/>\n        <source>Current Database</source>\n        <translation>現在のデータベース</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"10\"/>\n        <source>Add New Key to </source>\n        <translation>新しいキーを追加 </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"56\"/>\n        <location filename=\"../../qml/value-editor/editors/HashItemEditor.qml\" line=\"17\"/>\n        <source>Key:</source>\n        <translation>キー:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"66\"/>\n        <source>Type:</source>\n        <translation>型:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"107\"/>\n        <source>Or Import Value from the file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"113\"/>\n        <source>(Optional) Any file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"115\"/>\n        <source>Select file with value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"39\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"127\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"254\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"616\"/>\n        <source>Save</source>\n        <translation>保存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Edit Connections Group</source>\n        <translation>接続グループを編集</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Add New Connections Group</source>\n        <translation>接続グループの新規追加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"29\"/>\n        <source>Group Name:</source>\n        <translation>グループ名:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1091\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/editors/formatters/ValueFormatters.qml\" line=\"251\"/>\n        <source>Error</source>\n        <translation>エラー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"12\"/>\n        <source>Page</source>\n        <translation>ページ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"68\"/>\n        <source>Enter valid value</source>\n        <translation>有効な値を入力</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"286\"/>\n        <source>Formatting error</source>\n        <translation>フォーマットエラー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"291\"/>\n        <source>Unknown formatter error (Empty response)</source>\n        <translation>予期せぬフォーマッタのエラー&#x3000;(空の応答)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"363\"/>\n        <source>[Binary]</source>\n        <translation>[バイナリ]</translation>\n    </message>\n    <message>\n        <source> [Compressed: </source>\n        <translation type=\"vanished\"> [圧縮: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"544\"/>\n        <source>Copy to Clipboard</source>\n        <translation>クリップボードにコピー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"600\"/>\n        <source>Exit </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"601\"/>\n        <source>Full Screen Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"617\"/>\n        <source>Save Changes</source>\n        <translation>変更を保存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"719\"/>\n        <source>Search string</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find Next</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"764\"/>\n        <source>Regex</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"750\"/>\n        <source>Cannot find more results</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"400\"/>\n        <source>Try to decompress:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"401\"/>\n        <source>Decompressed:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"491\"/>\n        <source>Cannot decompress value using </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"751\"/>\n        <source>Cannot find any results</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"899\"/>\n        <source>Binary value is too large to display</source>\n        <translation>バイナリが大きすぎるため表示できません</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"366\"/>\n        <source>View as:</source>\n        <translation>表示形式:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"502\"/>\n        <source>Large value (&gt;150kB). Formatters are not available.</source>\n        <translation>値が大きすぎます(&gt;150kB)。フォーマッタは使用できません。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"18\"/>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"30\"/>\n        <source>Score</source>\n        <translation>ソース</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"10\"/>\n        <source>Bulk Operations Manager</source>\n        <translation type=\"unfinished\">バッチ処理マネージャ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Invalid RDB path</source>\n        <translation type=\"unfinished\">不正なRDBのパスです</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Please specify valid path to RDB file</source>\n        <translation type=\"unfinished\">RDBファイルへの正しいパスを指定してください</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"88\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"89\"/>\n        <source>Delete keys</source>\n        <translation type=\"unfinished\">キーを削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"98\"/>\n        <source>Set TTL</source>\n        <translation type=\"unfinished\">TTLを設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"106\"/>\n        <source>Copy keys to another database</source>\n        <translation type=\"unfinished\">他のデータベースにキーをコピー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"107\"/>\n        <source>Copy keys</source>\n        <translation type=\"unfinished\">キーをコピー</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"116\"/>\n        <source>Import data from rdb file</source>\n        <translation type=\"unfinished\">RDBファイルからデータをインポート</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"151\"/>\n        <source>Redis Server:</source>\n        <translation type=\"unfinished\">Redisサーバ:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"163\"/>\n        <source>Database number:</source>\n        <translation type=\"unfinished\">データベース番号:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"183\"/>\n        <source>Path to RDB file:</source>\n        <translation type=\"unfinished\">RDBファイルへのパス:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"194\"/>\n        <source>Path to dump.rdb file</source>\n        <translation>dump.rdbファイルへのパス</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"196\"/>\n        <source>Select dump.rdb</source>\n        <translation>dump.rdbを選択</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"205\"/>\n        <source>Select DB in RDB file:</source>\n        <translation type=\"unfinished\">RDBファイルからDBを選択</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Import keys that match &lt;b&gt;regex&lt;/b&gt;:</source>\n        <translation type=\"unfinished\">&lt;b&gt;正規表現&lt;/b&gt;に一致するキーをインポート:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Key pattern:</source>\n        <translation type=\"unfinished\">キーパターン:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"272\"/>\n        <source>Destination Redis Server:</source>\n        <translation type=\"unfinished\">Redisサーバの接続先:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"283\"/>\n        <source>Destination Redis Server Database Index:</source>\n        <translation type=\"unfinished\">Redisデータベースインデックスの接続先:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show matched keys</source>\n        <translation type=\"unfinished\">一致するキーを表示</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show Affected keys</source>\n        <translation type=\"unfinished\">影響するキーを表示</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Matched keys:</source>\n        <translation type=\"unfinished\">一致したキー:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Affected keys:</source>\n        <translation type=\"unfinished\">影響するキー:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"368\"/>\n        <source>Bulk Operation finished.</source>\n        <translation type=\"unfinished\">バッチ処理が完了しました。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"373\"/>\n        <source>Bulk Operation finished with errors</source>\n        <translation type=\"unfinished\">バッチ処理でエラーが発生しました</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"425\"/>\n        <source>Processed: </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"427\"/>\n        <source>Getting list of affected keys...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"475\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1097\"/>\n        <source>Success</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"501\"/>\n        <source>Confirmation</source>\n        <translation type=\"unfinished\">確認</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"502\"/>\n        <source>Do you really want to perform bulk operation?</source>\n        <translation type=\"unfinished\">バッチ処理を本当に実行しますか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"18\"/>\n        <source>ID</source>\n        <translation>ID</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"61\"/>\n        <source>Value (represented as JSON object)</source>\n        <translation>値(JSONオブジェクト形式)</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"127\"/>\n        <source>The row has been changed on server.Reload and try again.</source>\n        <translation>サーバ上のROWが更新されました。リロードしてからやりなおしてください。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/bulkoperationsmanager.cpp\" line=\"131\"/>\n        <source>Failed to perform actions on %1 keys. </source>\n        <translation>%1キーに対する処理が失敗しました</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"12\"/>\n        <source>Cannot copy key </source>\n        <translation>キーをコピーできません</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"123\"/>\n        <source>Source connection error</source>\n        <translation>ソース接続エラー</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"135\"/>\n        <source>Target connection error</source>\n        <translation>ターゲット接続エラー</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/deleteoperation.cpp\" line=\"11\"/>\n        <source>Cannot remove key </source>\n        <translation>キーを削除できません</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"17\"/>\n        <source>Cannot execute command </source>\n        <translation>コマンドを実行できません</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"26\"/>\n        <source>Invalid regexp for keys filter.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"39\"/>\n        <source>Cannot get the list of affected keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/ttloperation.cpp\" line=\"11\"/>\n        <source>Cannot set TTL for key </source>\n        <translation>TTLをキーに設定できません</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/abstractnamespaceitem.cpp\" line=\"381\"/>\n        <source>Your redis-server doesn&apos;t support &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; commands.</source>\n        <translation>あなたのRedisサーバは&lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt;コマンドをサポートしていません。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"138\"/>\n        <source>Key was added. Do you want to reload keys in selected namespace?</source>\n        <translation>キーを追加しました。選択されているネームスペースのキーをリロードしますか?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/FilePathInput.qml\" line=\"27\"/>\n        <source>Select File</source>\n        <translation>ファイルを選択</translation>\n    </message>\n    <message>\n        <source>Save to File</source>\n        <translation type=\"vanished\">ファイルに保存</translation>\n    </message>\n    <message>\n        <source>Save Value</source>\n        <translation type=\"vanished\">値を保存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"913\"/>\n        <source>Save value to file</source>\n        <translation>値をファイルに保存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Raw Value to File</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Formatted Value to File</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Raw Value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Formatted Value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"51\"/>\n        <source>Value was saved to file:</source>\n        <translation>値をファイルに保存しました:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/abstractoperation.cpp\" line=\"38\"/>\n        <source>Cannot connect to redis-server</source>\n        <translation>Redisサーバに接続できません</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"13\"/>\n        <source>Edit Connection Group</source>\n        <translation>接続グループの編集</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"17\"/>\n        <source>Delete Connection Group</source>\n        <translation>接続グループの削除</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/servergroup.cpp\" line=\"58\"/>\n        <source>Do you really want to delete group &lt;b&gt;with all connections&lt;/b&gt;?</source>\n        <translation>グループを本当に削除しますか？&#x3000;&lt;b&gt;グループの接続情報もすべて失われます&lt;/b&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"8\"/>\n        <source>Order of elements:</source>\n        <translation>要素の順序:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"20\"/>\n        <source>Default</source>\n        <translation>デフォルト</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"21\"/>\n        <source>Reverse</source>\n        <translation>降順</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"28\"/>\n        <source>Start date should be less than End date</source>\n        <translation>開始日は終了日より前でなければなりません</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"136\"/>\n        <source>Apply filter</source>\n        <translation>フィルタを適用</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"147\"/>\n        <source>Network is not accessible. Please ensure that you have internet access and try again.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"190\"/>\n        <source>Invalid login or password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"200\"/>\n        <source>Too many requests from your IP</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"206\"/>\n        <source>Unknown error. Status code %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"321\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"734\"/>\n        <source>Cannot parse server reply</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"341\"/>\n        <source>Cannot validate token</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"347\"/>\n        <source>Cannot login - %1. &lt;br/&gt; Please try again or contact  &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"588\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"614\"/>\n        <source>Cannot save the update. Disk is full or download folder is not writable.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"666\"/>\n        <source>Download was canceled</source>\n        <translation type=\"unfinished\">ダウンロードをキャンセルしました</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"673\"/>\n        <source>Network error</source>\n        <translation type=\"unfinished\">ネットワークエラー</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"718\"/>\n        <source>Expired activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"731\"/>\n        <source>Invalid activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"30\"/>\n        <source>&lt;span style=&quot;font-size: 11px;&quot;&gt;Powered by awesome &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;open-source software&lt;/a&gt; and &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"11\"/>\n        <source>Getting Started</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"42\"/>\n        <source>Thank you for choosing RESP.app. Let&apos;s make your Redis experience better.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"60\"/>\n        <source>Connect to Redis-Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"73\"/>\n        <source>Read the Docs</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/loadmoreitem.cpp\" line=\"12\"/>\n        <source>Load more keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"19\"/>\n        <source>SSH Passphrase</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"21\"/>\n        <source>Unknown</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <source>Passphrase</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"71\"/>\n        <source>Continue</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"24\"/>\n        <source>Yes</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"32\"/>\n        <source>No</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"19\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"25\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"49\"/>\n        <source>Trial is active till</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"58\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"59\"/>\n        <source>Licensed to</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"66\"/>\n        <source>Subscription is active until:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Manage Subscription</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/ColorInput.qml\" line=\"43\"/>\n        <source>Select</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/UnsupportedDataType.qml\" line=\"24\"/>\n        <source>Unsupported Redis Data type </source>\n        <translation>サポートしていないRedisのデータ型です</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"163\"/>\n        <source>Cannot delete key:\n\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "src/resources/translations/rdm_uk_UA.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"uk_UA\">\n<context>\n    <name>QObject</name>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"360\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"397\"/>\n        <source>Cannot connect to cluster node %1:%2</source>\n        <translation>Не вдається підключитися до вузла кластера %1:%2</translation>\n    </message>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"408\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"435\"/>\n        <source>Cannot flush db (%1): %2</source>\n        <translation>Не вдається очистити базу даних (%1): %2</translation>\n    </message>\n</context>\n<context>\n    <name>RESP</name>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"82\"/>\n        <source>Settings directory is not writable</source>\n        <translation>Каталог з налаштуваннями недоступний для запису</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"84\"/>\n        <source>RESP.app can&apos;t save connections file to settings directory. Please change file permissions or restart RESP.app as administrator.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>RDM can&apos;t save connections file to settings directory. Please change file permissions or restart RDM as administrator.</source>\n        <translation type=\"vanished\">RDM не може зберегти файл підключень до каталогу налаштувань. Змініть файлові дозволи або перезапустіть RDM як адміністратор.</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"160\"/>\n        <source>Cannot parse scan response</source>\n        <translation>Не вдається розібрати відповідь на команду SCAN</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"328\"/>\n        <source>Server returned unexpected response: </source>\n        <translation>Сервер повернув несподівану відповідь: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"106\"/>\n        <source>Cannot set TTL for key %1</source>\n        <translation>Не вдається встановити TTL для ключа %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"81\"/>\n        <source>Cannot rename key %1: %2</source>\n        <translation>Не вдається перейменувати ключ %1: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"125\"/>\n        <source>Cannot persist key &apos;%1&apos;. &lt;br&gt; Key does not exist or does not have an assigned TTL value</source>\n        <translation>Не вдається зробити ключ &apos;%1&apos; постійним. &lt;br&gt; Ключ не існує або не має присвоєного значення TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"274\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"285\"/>\n        <source>Cannot load rows for key %1: %2</source>\n        <translation>Не вдається завантажити рядки для ключа %1: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"42\"/>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"75\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"14\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"41\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"12\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"33\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"44\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"77\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"48\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"59\"/>\n        <source>Invalid row</source>\n        <translation>Неприпустимий рядок</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"113\"/>\n        <source>Value with the same key already exists</source>\n        <translation>Значення з таким самим ключем вже існує</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"184\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"340\"/>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"151\"/>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"84\"/>\n        <source>Connection error: </source>\n        <translation>Помилка підключення: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"136\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"136\"/>\n        <source>Data was loaded from server partially.</source>\n        <translation>Дані були завантажені з сервера частково.</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"26\"/>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"38\"/>\n        <source>Cannot load key %1, connection error occurred: %2</source>\n        <translation>Не вдається завантажити ключ %1, сталася помилка підключення: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"49\"/>\n        <source>Cannot load key %1 because it doesn&apos;t exist in database. Please reload connection tree and try again.</source>\n        <translation>Не вдається завантажити ключ %1, оскільки він не існує в базі даних. Перезавантажте дерево підключення та повторіть спробу.</translation>\n    </message>\n    <message>\n        <source>Cannot load TTL for key %1, connection error occurred: %2</source>\n        <translation type=\"vanished\">Не вдається завантажити TTL для ключа %1, сталася помилка підключення: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"78\"/>\n        <source>Cannot retrieve type of the key: </source>\n        <translation>Не вдається отримати тип ключа: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"122\"/>\n        <source>Cannot open file with key value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Unsupported Redis Data type %1</source>\n        <translation type=\"vanished\">Тип даних Redis %1 не підтримується</translation>\n    </message>\n    <message>\n        <source>Cannot retrive type of the key: </source>\n        <translation type=\"vanished\">Не вдається отримати тип ключа: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"129\"/>\n        <source>Cannot connect to server &apos;%1&apos;. Check log for details.</source>\n        <translation>Не вдається підключитися до сервера &apos;%1&apos;. Перевірте журнал для деталей.</translation>\n    </message>\n    <message>\n        <source>Open Source version of RDM &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation type=\"vanished\">Версія RDM з відкритим кодом &lt;b&gt;не підтримує тунелювання SSH&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; Щоб отримати повнофункціональну програму, придбайте підписку на &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Кожна окрема підписка дає нам кошти для продовження процесу розробки та надання підтримки нашим користувачам. &lt;br /&gt;Якщо у вас виникли запитання, будь ласка, зв&apos;яжіться з нами за адресою &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; або приєднуйтеся до &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram-чату&lt;/a&gt;.</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"139\"/>\n        <source>Open Source version of RESP.app &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"229\"/>\n        <source>Cannot load keys: %1</source>\n        <translation>Не вдається завантажити ключі: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"336\"/>\n        <source>Delete key error: %1</source>\n        <translation>Помилка видалення ключа: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"477\"/>\n        <source>Cannot determine amount of used memory by key: %1</source>\n        <translation>Не вдається визначити обсяг використаної пам&apos;яті за ключем: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"416\"/>\n        <source>Cannot flush database: </source>\n        <translation>Не вдається очистити базу даних: </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/common/tabmodel.cpp\" line=\"43\"/>\n        <source>Invalid Connection. Check connection settings.</source>\n        <translation>Недійсне з’єднання. Перевірте налаштування підключення.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"251\"/>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carefully or change limit in settings.</source>\n        <translation>Автоматичне оновлення було вимкнено через перевищення обмеження ключів. Будь ласка, вказуйте фільтр обережніше або змініть обмеження в налаштуваннях.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"304\"/>\n        <source>Key was added. Do you want to reload keys in selected database?</source>\n        <translation>Ключ додано. Хочете перезавантажити ключі в обраній базі даних?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"312\"/>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"143\"/>\n        <source>Key was added</source>\n        <translation>Ключ додано</translation>\n    </message>\n    <message>\n        <source>Another operation is currently in progress</source>\n        <translation type=\"vanished\">Інша операція вже обробляється</translation>\n    </message>\n    <message>\n        <source>Please wait until another operation will be finished.</source>\n        <translation type=\"vanished\">Зачекайте, доки не буде закінчена інша операція.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"327\"/>\n        <source>Do you really want to remove all keys from this database?</source>\n        <translation>Ви дійсно хочете видалити всі ключі з цієї бази даних?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"73\"/>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"75\"/>\n        <source>Cannot load databases:\n\n</source>\n        <translation>Не вдається завантажити бази даних:\n\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"250\"/>\n        <source>Live update was disabled</source>\n        <translation>Автоматичне оновлення було вимкнено</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"190\"/>\n        <source>Rename key</source>\n        <translation>Перейменувати ключ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"199\"/>\n        <source>New name:</source>\n        <translation>Нова назва:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"21\"/>\n        <source>Total pages: </source>\n        <translation>Всього сторінок: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"45\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"222\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"358\"/>\n        <source>Size: </source>\n        <translation>Розмір: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"228\"/>\n        <source>TTL:</source>\n        <translation>TTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"233\"/>\n        <source>Set key TTL</source>\n        <translation>Встановити TTL ключа</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"242\"/>\n        <source>New TTL:</source>\n        <translation>Новий TTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"298\"/>\n        <source>Delete</source>\n        <translation>Видалити</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"23\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"303\"/>\n        <source>Delete key</source>\n        <translation>Видалити ключ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"38\"/>\n        <source>Changes are not saved</source>\n        <translation>Зміни не зберігаються</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"39\"/>\n        <source>Do you want to close key tab without saving changes?</source>\n        <translation>Ви хочете закрити вкладку з ключем без збереження змін?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"260\"/>\n        <source>Persist key</source>\n        <translation>Зробити ключ постійним</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"304\"/>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"153\"/>\n        <source>Do you really want to delete this key?</source>\n        <translation>Ви дійсно хочете видалити цей ключ?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"140\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"318\"/>\n        <source>Reload Value</source>\n        <translation>Перезавантажити значення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"22\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"31\"/>\n        <source>Add Row</source>\n        <translation>Додати рядок</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"30\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"526\"/>\n        <source>Add Element to HLL</source>\n        <translation>Додати елемент до HLL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"68\"/>\n        <source>Add</source>\n        <translation>Додати</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"101\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"122\"/>\n        <source>Delete row</source>\n        <translation>Видалити рядок</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"107\"/>\n        <source>The row is the last one in the key. After removing it key will be deleted.</source>\n        <translation>Рядок - останній у ключі. Після його видалення ключ буде видалений.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"109\"/>\n        <source>Do you really want to remove this row?</source>\n        <translation>Ви дійсно хочете видалити цей рядок?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"164\"/>\n        <source>Search on page...</source>\n        <translation>Шукати на сторінці...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"191\"/>\n        <source>Full Search</source>\n        <translation>Повний пошук</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"191\"/>\n        <source>Value and Console tabs related to this connection will be closed. Do you want to continue?</source>\n        <translation>Вкладки значень та консоль, пов’язані з цим з’єднанням, будуть закриті. Ви хочете продовжити?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"204\"/>\n        <source>Do you really want to delete connection?</source>\n        <translation>Ви дійсно хочете видалити підключення?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"13\"/>\n        <source>Connected to cluster.\n</source>\n        <translation>Підключено до кластера.\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"16\"/>\n        <source>Connected.\n</source>\n        <translation>Підключено.\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"60\"/>\n        <source>Switch to %1 mode. Close console tab to stop listen for messages.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Switch to Pub/Sub mode. Close console tab to stop listen for messages.</source>\n        <translation type=\"vanished\">Перехід у режим Pub/Sub. Закрийте вкладку консолі, щоб зупинити прослуховування повідомлень.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"69\"/>\n        <source>Subscribe error: %1</source>\n        <translation>Помилка підписки на канал: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/server-actions/serverstatsmodel.cpp\" line=\"36\"/>\n        <source>Server %0</source>\n        <translation>Сервер %0</translation>\n    </message>\n    <message>\n        <source>Can&apos;t find formatter with name: %1</source>\n        <translation type=\"vanished\">Не вдається знайти форматер із назвою: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"109\"/>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"147\"/>\n        <source>Can&apos;t find formatter: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"115\"/>\n        <source>Invalid callback</source>\n        <translation>Недійсний зворотний виклик</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"204\"/>\n        <source>Can&apos;t load list of available formatters from extension server: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"260\"/>\n        <source>Can&apos;t encode value: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Cannot decode value using %1 formatter. </source>\n        <translation type=\"vanished\">Не вдається декодувати значення за допомогою форматера %1. </translation>\n    </message>\n    <message>\n        <source>Cannot validate value using %1 formatter.</source>\n        <translation type=\"vanished\">Неможливо перевірити значення за допомогою форматера %1.</translation>\n    </message>\n    <message>\n        <source>Cannot encode value using %1 formatter. </source>\n        <translation type=\"vanished\">Не вдається закодувати значення за допомогою форматера %1. </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"26\"/>\n        <source>Loading key: %1 from db %2</source>\n        <translation>Завантажується ключ: %1 з БД %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"68\"/>\n        <source>Cannot open value tab</source>\n        <translation>Не вдається відкрити вкладку значення</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"97\"/>\n        <source>Connection error</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"115\"/>\n        <source>Connection error. Can&apos;t open value tab. </source>\n        <translation>Помилка підключення. Не вдається відкрити вкладку значення. </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"176\"/>\n        <source>Cannot reload key value: %1</source>\n        <translation>Не вдається перезавантажити значення ключа: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"228\"/>\n        <source>Cannot load key value: %1</source>\n        <translation>Не вдається завантажити значення ключа: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"29\"/>\n        <source>Connect to Redis Server</source>\n        <translation>Підключіться до сервера Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"117\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"205\"/>\n        <source>Import</source>\n        <translation>Імпорт</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"50\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"69\"/>\n        <source>Import Connections</source>\n        <translation>Імпортувати підключення</translation>\n    </message>\n    <message>\n        <source>Export</source>\n        <translation type=\"vanished\">Експорт</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"58\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"74\"/>\n        <source>Export Connections</source>\n        <translation>Експорт підключень</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"100\"/>\n        <source>Report issue</source>\n        <translation>Повідомити про баг</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"107\"/>\n        <source>Documentation</source>\n        <translation>Документація</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"114\"/>\n        <source>Join Telegram Chat</source>\n        <translation>Приєднуйтесь до чату Telegram</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"121\"/>\n        <source>Follow</source>\n        <translation>Слідкувати</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"128\"/>\n        <source>Star on GitHub!</source>\n        <translation>Підтримати на GitHub!</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"136\"/>\n        <source>Log</source>\n        <translation>Журнал</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"144\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"13\"/>\n        <source>Extension Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"154\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"13\"/>\n        <source>Settings</source>\n        <translation>Налаштування</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>New Connection Settings</source>\n        <translation>Налаштування нового підключення</translation>\n    </message>\n    <message>\n        <source>Connection Wizard</source>\n        <translation type=\"vanished\">Майстер підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"151\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"48\"/>\n        <source>Connection Settings</source>\n        <translation>Налаштування підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"180\"/>\n        <source>Create connection from Redis URL</source>\n        <translation>Створити підключення з Redis URL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"233\"/>\n        <source>Learn more about Redis URL:  </source>\n        <translation>Дізнайтеся більше про Redis URL:  </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"240\"/>\n        <source>Connection guides</source>\n        <translation>Інструкції з підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"260\"/>\n        <source>Local or Public Redis</source>\n        <translation>Локальний або публічний Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"276\"/>\n        <source>Redis with SSL/TLS</source>\n        <translation>Redis із SSL/TLS</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"292\"/>\n        <source>SSH tunnel</source>\n        <translation>Тунель SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"308\"/>\n        <source>UNIX socket</source>\n        <translation>Сокет UNIX</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"395\"/>\n        <source>Cannot figure out how to connect to your redis-server?</source>\n        <translation>Не можете зрозуміти, як підключитися до вашого Redis-сервера?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"401\"/>\n        <source>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Read the Docs&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;Contact Support&lt;/a&gt; or ask for help in our &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram Group&lt;/a&gt;</source>\n        <translation>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Ознайомтеся з документацією&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;зверніться за підтримкою&lt;/a&gt; або зверніться за допомогою до нашої &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram-групи&lt;/a&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"413\"/>\n        <source>Don&apos;t have running Redis?</source>\n        <translation>Не маєте запущеного Redis-сервера?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"419\"/>\n        <source>Spin up hassle-free Redis on Digital Ocean</source>\n        <translation>Запустити Redis на Digital Ocean</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"431\"/>\n        <source>Skip</source>\n        <translation>Пропустити</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"466\"/>\n        <source>Name:</source>\n        <translation>Назва:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"472\"/>\n        <source>Connection Name</source>\n        <translation>Назва підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"478\"/>\n        <source>Address:</source>\n        <translation>Адреса:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"483\"/>\n        <source>redis-server host</source>\n        <translation>Хост redis-сервера</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"494\"/>\n        <source>For better network performance please use 127.0.0.1</source>\n        <translation>Для кращої роботи мережі використовуйте 127.0.0.1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"507\"/>\n        <source>(Optional) redis-server authentication password</source>\n        <translation>(Необов’язково) Пароль автентифікації сервера redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"512\"/>\n        <source>Username:</source>\n        <translation>Ім&apos;я користувача:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"518\"/>\n        <source>(Optional) redis-server authentication username (Redis &gt;6.0)</source>\n        <translation>(Необов’язково) ім’я користувача для автентифікації сервера redis (Redis&gt; 6.0)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"526\"/>\n        <source>Security</source>\n        <translation>Безпека</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"573\"/>\n        <source>Public Key:</source>\n        <translation>Відкритий ключ:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"579\"/>\n        <source>(Optional) Public Key in PEM format</source>\n        <translation>(Необов’язково) Відкритий ключ у форматі PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"581\"/>\n        <source>Select public key in PEM format</source>\n        <translation>Виберіть відкритий ключ у форматі PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"592\"/>\n        <source>(Optional) Private Key in PEM format</source>\n        <translation>(Необов’язково) Закритий ключ у форматі PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"594\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"703\"/>\n        <source>Select private key in PEM format</source>\n        <translation>Виберіть закритий ключ у форматі PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"599\"/>\n        <source>Authority:</source>\n        <translation>АЦСК:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"605\"/>\n        <source>(Optional) Authority in PEM format</source>\n        <translation>(Необов’язково) Файл АЦСК у форматі PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"607\"/>\n        <source>Select authority file in PEM format</source>\n        <translation>Виберіть файл АЦСК у форматі PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"553\"/>\n        <source>SSH Tunnel</source>\n        <translation>SSH тунель</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"630\"/>\n        <source>SSH Address:</source>\n        <translation>Адреса SSH:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"634\"/>\n        <source>Remote Host with SSH server</source>\n        <translation>Віддалений хост із сервером SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"642\"/>\n        <source>SSH User:</source>\n        <translation>Користувач SSH:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"648\"/>\n        <source>Valid SSH User Name</source>\n        <translation>Дійсне ім&apos;я користувача SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"586\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"683\"/>\n        <source>Private Key</source>\n        <translation>Приватний ключ</translation>\n    </message>\n    <message>\n        <source>Import connection parameters from Redis connection string</source>\n        <translation type=\"vanished\">Імпортувати параметри з&apos;єднання із рядка з&apos;єднання Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"701\"/>\n        <source>Path to Private Key in PEM format</source>\n        <translation>Шлях до приватного ключа у форматі PEM</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"711\"/>\n        <source>&lt;b&gt;Tip:&lt;/b&gt; Use &lt;code&gt;⌘ + Shift + .&lt;/code&gt; to show hidden files and folders in dialog</source>\n        <translation>&lt;b&gt; Порада: &lt;/b&gt; Використовуйте &lt;code&gt; ⌘ + Shift +. &lt;/code&gt;, щоб показати приховані файли та папки у діалоговому вікні</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"87\"/>\n        <source>Password</source>\n        <translation>Пароль</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"737\"/>\n        <source>SSH User Password</source>\n        <translation>Пароль користувача SSH</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"759\"/>\n        <source>Enable TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</source>\n        <translation>Увімкнути TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"156\"/>\n        <source>Advanced Settings</source>\n        <translation>Розширені налаштування</translation>\n    </message>\n    <message>\n        <source>Sign in with resp.app account</source>\n        <translation type=\"vanished\">Увійдіть за допомогою облікового запису resp.app</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"231\"/>\n        <source>Renew your subscription</source>\n        <translation>Поновити підписку</translation>\n    </message>\n    <message>\n        <source>Your trial has ended.</source>\n        <translation type=\"vanished\">Ваш пробний період закінчився.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"234\"/>\n        <source>You have no active subscription</source>\n        <translation>У вас немає активної підписки</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"237\"/>\n        <source>No internet connection</source>\n        <translation>Немає підключення до інтернету</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"240\"/>\n        <source>Your trial has ended</source>\n        <translation>Ваш пробний період закінчився</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"252\"/>\n        <source>To use this version you need to renew your subscription.</source>\n        <translation>Для використання цієї версії вам потрібно поновити підписку.</translation>\n    </message>\n    <message>\n        <source>Please make sure that RDM is not blocked by a firewall and you have an internet connection.</source>\n        <translation type=\"vanished\">Будь ласка, переконайтеся, що RDM не заблоковано брандмауером, а також перевірте наявність підключення до інтернету.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"257\"/>\n        <source>If you’re behind a proxy please enable </source>\n        <translation>Якщо ви за проксі-сервером, увімкніть </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"261\"/>\n        <source> option before sign-in.</source>\n        <translation> опцію перед входом.</translation>\n    </message>\n    <message>\n        <source>Please purchase a subscription to continue using RDM.</source>\n        <translation type=\"vanished\">Будь ласка, придбайте передплату, щоб продовжити використання RDM.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"11\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"317\"/>\n        <source>Sign in with RESP.app account</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"255\"/>\n        <source>Please make sure that RESP.app is not blocked by a firewall and you have an internet connection.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"264\"/>\n        <source>Please purchase a subscription to continue using RESP.app.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"268\"/>\n        <source>If you have any questions please contact support </source>\n        <translation>Якщо у вас є будь-які запитання, звертайтесь до служби підтримки </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"279\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Renew Subscription</source>\n        <translation>Поновити підписку</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"280\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"32\"/>\n        <source>Buy Subscription</source>\n        <translation>Купити підписку</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"293\"/>\n        <source>Try Again</source>\n        <translation>Спробувати ще раз</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"331\"/>\n        <source>Email:</source>\n        <translation>Електронна адреса:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"347\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"501\"/>\n        <source>Password:</source>\n        <translation>Пароль:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"376\"/>\n        <source>Forgot password?</source>\n        <translation>Забули пароль?</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"422\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"444\"/>\n        <source>Offline Activation</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"465\"/>\n        <source>Paste Activation code here</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"481\"/>\n        <source>Where can I find my activation code?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"490\"/>\n        <source>Activate</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"495\"/>\n        <source>Please enter valid activation code.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Don’t have an account? Sign up</source>\n        <translation type=\"vanished\">Не маєте акаунту? Зареєструватися</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"393\"/>\n        <source>Application will be restarted to apply this setting.</source>\n        <translation>Додаток буде перезапущено, щоб застосувати цей параметр.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"404\"/>\n        <source>Sign In</source>\n        <translation>Увійти</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"409\"/>\n        <source>Please enter email &amp; password to sign in.</source>\n        <translation>Введіть адресу електронної пошти та пароль для входу.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"540\"/>\n        <source>SSL / TLS</source>\n        <translation>SSL / TLS</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"612\"/>\n        <source>Enable strict mode:</source>\n        <translation>Увімкнути суворий режим:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"657\"/>\n        <source>Use SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"667\"/>\n        <source>(Optional) Custom SSH Agent Path</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"669\"/>\n        <source>Select SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"678\"/>\n        <source>Additional configuration is required to enable SSH Agent support</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"734\"/>\n        <source>Passphrase for provided private key</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"736\"/>\n        <source>Password request will be prompt prior to connection</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"747\"/>\n        <source>Ask for password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"796\"/>\n        <source>Keys loading</source>\n        <translation>Завантаження ключів</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"800\"/>\n        <source>Default filter:</source>\n        <translation>Фільтр за замовчуванням:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"806\"/>\n        <source>Pattern which defines loaded keys from redis-server</source>\n        <translation>Шаблон, який визначає завантажені ключі з redis-сервера</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"812\"/>\n        <source>Namespace Separator:</source>\n        <translation>Розділювач простору імен:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"819\"/>\n        <source>Separator used for namespace extraction from keys</source>\n        <translation>Розділювач для вилучення простору імен із ключів</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"825\"/>\n        <source>Timeouts &amp; Limits</source>\n        <translation>Час очікування та ліміти</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"829\"/>\n        <source>Connection Timeout (sec):</source>\n        <translation>Час очікування підключення (сек):</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"842\"/>\n        <source>Execution Timeout (sec):</source>\n        <translation>Час очікування виконання (сек):</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"853\"/>\n        <source>Databases discovery limit:</source>\n        <translation>Ліміт на завантаження баз данних:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"867\"/>\n        <source>Cluster</source>\n        <translation>Кластер</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"871\"/>\n        <source>Change host on cluster redirects:</source>\n        <translation>Змінювати хост при переспрямуваннях кластера:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"881\"/>\n        <source>Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"885\"/>\n        <source>Default value formatter:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"901\"/>\n        <source>Auto detect (JSON / Plain Text / HEX)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"902\"/>\n        <source>Last selected</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"903\"/>\n        <source>Select formatter ...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"952\"/>\n        <source>Appearance</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"956\"/>\n        <source>Icon color:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1021\"/>\n        <source>Invalid settings detected!</source>\n        <translation>Виявлено невірні налаштування!</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"992\"/>\n        <source>Test Connection</source>\n        <translation>Тестове підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"144\"/>\n        <source>How to connect</source>\n        <translation>Як підключитися</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/OkDialogOverlay.qml\" line=\"20\"/>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"111\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1029\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"163\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"319\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"903\"/>\n        <source>OK</source>\n        <translation>OK</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"294\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"508\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"402\"/>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"44\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"61\"/>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"89\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1057\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"175\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"331\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"172\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"89\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"268\"/>\n        <source>Cancel</source>\n        <translation>Скасувати</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"48\"/>\n        <source>General</source>\n        <translation>Загальні</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"53\"/>\n        <source>Application will be restarted to apply these settings.</source>\n        <translation>Додаток буде перезапущено, щоб застосувати зміни в налаштуваннях.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"73\"/>\n        <source>Language</source>\n        <translation>Мова</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"85\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"174\"/>\n        <source>Font</source>\n        <translation>Шрифт</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"97\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"186\"/>\n        <source>Font Size</source>\n        <translation>Розмір шрифту</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"110\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"124\"/>\n        <source>Dark Mode</source>\n        <translation>Темний режим</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"200\"/>\n        <source>Maximum Formatted Value Size</source>\n        <translation>Максимальний розмір форматованого значення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"201\"/>\n        <source>Size in bytes</source>\n        <translation>Розмір у байтах</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"213\"/>\n        <source>Maximum amount of items per page</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"258\"/>\n        <source>Show only last part for namespaced keys</source>\n        <translation>Показати лише останню частину ключів з простором імен</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"259\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"392\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"138\"/>\n        <source>Use system proxy settings</source>\n        <translation>Використовувати налаштування системного проксі</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"150\"/>\n        <source>Use system proxy only for HTTP(S) requests</source>\n        <translation>Використовувати системний проксі лише для запитів HTTP(S)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"156\"/>\n        <source>Value Editor</source>\n        <translation>Редактор значень</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"218\"/>\n        <source>Connections Tree</source>\n        <translation>Дерево підключень</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"236\"/>\n        <source>Show namespaced keys on top</source>\n        <translation>Спочатку показувати ключі з простором імен</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"246\"/>\n        <source>Reopen namespaces on reload</source>\n        <translation>Повторно відкрити простори імен при перезавантаженні</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"247\"/>\n        <source>(Disable to improve treeview performance)</source>\n        <translation>(Вимкніть, щоб пришвидшити роботу дерева ключів)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"270\"/>\n        <source>Limit for SCAN command</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"282\"/>\n        <source>Maximum amount of rendered child items</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"294\"/>\n        <source>Live update maximum allowed keys</source>\n        <translation>Максимальна кількість ключів для автоматичного оновлення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"306\"/>\n        <source>Live update interval (in seconds)</source>\n        <translation>Інтервал автоматичного оновлення (у секундах)</translation>\n    </message>\n    <message>\n        <source>External Value View Formatters</source>\n        <translation type=\"vanished\">Зовнішні форматери</translation>\n    </message>\n    <message>\n        <source>Formatters path: %0</source>\n        <translation type=\"vanished\">Шлях до форматерів:%0</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"61\"/>\n        <source>Server Url:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"75\"/>\n        <source>Basic Auth:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"81\"/>\n        <source>User</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"99\"/>\n        <source>Response timeout  (in seconds)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"109\"/>\n        <source>Available Data Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"117\"/>\n        <source>Reload</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"135\"/>\n        <source>Id</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"141\"/>\n        <source>Name</source>\n        <translation>Назва</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"147\"/>\n        <source>Read Only</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"29\"/>\n        <source>Version</source>\n        <translation>Версія</translation>\n    </message>\n    <message>\n        <source>Explore RDM</source>\n        <translation type=\"vanished\">Дослідіть RDM</translation>\n    </message>\n    <message>\n        <source>Before using RDM take a look on the %1</source>\n        <translation type=\"vanished\">Перед використанням RDM ознайомтеся з %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1001\"/>\n        <source>Quick Start Guide</source>\n        <translation>Інструкція для початківців</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"134\"/>\n        <source>Successful connection to redis-server</source>\n        <translation>Успішне підключення до redis-сервера</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"137\"/>\n        <source>Can&apos;t connect to redis-server</source>\n        <translation>Не вдається підключитися до redis-сервера</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"321\"/>\n        <source>Add Group</source>\n        <translation>Додати групу</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"336\"/>\n        <source>Regroup connections</source>\n        <translation>Перегрупувати підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"358\"/>\n        <source>Exit Regroup Mode</source>\n        <translation>Вийти з режиму перегрупування</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"370\"/>\n        <location filename=\"../../qml/common/PasswordInput.qml\" line=\"29\"/>\n        <source>Show password</source>\n        <translation>Показати пароль</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/TreeItemDelegate.qml\" line=\"220\"/>\n        <source> (Removed)</source>\n        <translation> (Видалено)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"77\"/>\n        <source>Open Keys Filter</source>\n        <translation>Відкрити фільтр ключів</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"81\"/>\n        <source>Reload Keys in Database</source>\n        <translation>Перезавантажити ключі в базі даних</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"85\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"30\"/>\n        <source>Add New Key</source>\n        <translation>Додати новий ключ</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Disable Live Update</source>\n        <translation>Вимкнути автоматичне оновлення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Enable Live Update</source>\n        <translation>Увімкнути автоматине оновлення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"95\"/>\n        <source>Open Console</source>\n        <translation>Відкрити консоль</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"98\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"32\"/>\n        <source>Analyze Used Memory</source>\n        <translation>Проаналізувати використану пам’ять</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"100\"/>\n        <source>Bulk Operations</source>\n        <translation>Масові операції</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"121\"/>\n        <source>Flush Database</source>\n        <translation>Очистити базу даних</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"124\"/>\n        <source>Delete keys with filter</source>\n        <translation>Видалити ключі з фільтром</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"97\"/>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"127\"/>\n        <source>Set TTL for multiple keys</source>\n        <translation>Встановити TTL для кількох ключів</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"130\"/>\n        <source>Copy keys from this database to another</source>\n        <translation>Скопіювати ключі з цієї бази даних в іншу</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"133\"/>\n        <source>Import keys from RDB file</source>\n        <translation>Імпортувати ключі з файлу rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"136\"/>\n        <source>Back</source>\n        <translation>Назад</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"22\"/>\n        <source>Copy Key Name</source>\n        <translation>Копіювати назву ключа</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"29\"/>\n        <source>Reload Namespace</source>\n        <translation>Перезавантажити простір імен</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"31\"/>\n        <source>Copy Namespace Pattern</source>\n        <translation>Скопіювати шаблон простору імен</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"33\"/>\n        <source>Delete Namespace</source>\n        <translation>Видалити простір імен</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"71\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"24\"/>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"14\"/>\n        <source>Disconnect</source>\n        <translation>Відключитися</translation>\n    </message>\n    <message>\n        <source>Server Info</source>\n        <translation type=\"vanished\">Інформація про сервер</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"20\"/>\n        <source>Reload Server</source>\n        <translation>Перезавантажити підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"24\"/>\n        <source>Unload All Data</source>\n        <translation>Вивантажити всі дані</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"28\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>Edit Connection Settings</source>\n        <translation>Редагувати налаштування підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"32\"/>\n        <source>Duplicate Connection</source>\n        <translation>Створити дублікат підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"36\"/>\n        <source>Delete Connection</source>\n        <translation>Видалити підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"21\"/>\n        <source>Connecting...</source>\n        <translation>Підключення...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"193\"/>\n        <source>Clear</source>\n        <translation>Очистити</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"234\"/>\n        <source>Arguments</source>\n        <translation>Параметри</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"240\"/>\n        <source>Description</source>\n        <translation>Опис</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"246\"/>\n        <source>Available since</source>\n        <translation>Доступно з</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"297\"/>\n        <source>Close</source>\n        <translation>Закрити</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"108\"/>\n        <source>View Server Info</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"130\"/>\n        <source>Redis Version</source>\n        <translation>Версія Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"169\"/>\n        <source>Used memory</source>\n        <translation>Використана пам’ять</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"182\"/>\n        <source>Cmd Processed</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"203\"/>\n        <source>Monitor Commands</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"242\"/>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"319\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"105\"/>\n        <source>Clients</source>\n        <translation>Клієнти</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"377\"/>\n        <source>Server Actions</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Commands Processed</source>\n        <translation type=\"vanished\">Команд оброблено</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"143\"/>\n        <source>Uptime</source>\n        <translation>Час роботи</translation>\n    </message>\n    <message>\n        <source>Total Keys</source>\n        <translation type=\"vanished\">Усього ключів</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"156\"/>\n        <source>Hit Ratio</source>\n        <translation>Коефіцієнт влучень</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"262\"/>\n        <source>Server Stats</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"281\"/>\n        <source>Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"321\"/>\n        <source> day(s)</source>\n        <translation> днів</translation>\n    </message>\n    <message>\n        <source>Info</source>\n        <translation type=\"vanished\">Інформація</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"53\"/>\n        <source>Commands Per Second</source>\n        <translation>Команд на секунду</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"67\"/>\n        <source>Ops/s</source>\n        <translation>Операцій/с</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"91\"/>\n        <source>Connected Clients</source>\n        <translation>Підключені клієнти</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"129\"/>\n        <source>Memory Usage</source>\n        <translation>Використання пам&apos;яті</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"141\"/>\n        <source>Mb</source>\n        <translation>Mb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"170\"/>\n        <source>Network Input</source>\n        <translation>Вхідні дані</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"182\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"218\"/>\n        <source>Kb/s</source>\n        <translation>Kb/s</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"206\"/>\n        <source>Network Output</source>\n        <translation>Вихідні дані</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"242\"/>\n        <source>Total Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"256\"/>\n        <source>Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Keys</source>\n        <translation type=\"vanished\">Ключі</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"36\"/>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"28\"/>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"39\"/>\n        <source>Auto Refresh</source>\n        <translation>Автоматичне оновлення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"66\"/>\n        <source>Property</source>\n        <translation>Властивість</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"72\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"22\"/>\n        <source>Value</source>\n        <translation>Значення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"68\"/>\n        <source>Subscribe in Console</source>\n        <translation>Переглянути в консолі</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"221\"/>\n        <source>Slowlog</source>\n        <translation>Slowlog</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"299\"/>\n        <source>Pub/Sub Channels</source>\n        <translation>Pub/Sub канали</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"38\"/>\n        <source>Enable</source>\n        <translation>Увімкнути</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"57\"/>\n        <source>Channel Name</source>\n        <translation>Назва каналу</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"54\"/>\n        <source>Command</source>\n        <translation>Команда</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"71\"/>\n        <source>Processed at</source>\n        <translation>Оброблено за</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"86\"/>\n        <source>Execution Time (μs)</source>\n        <translation>Час виконання (мкс)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"51\"/>\n        <source>Client Address</source>\n        <translation>Адреса клієнта</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"57\"/>\n        <source>Age (sec)</source>\n        <translation>Вік (сек)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"63\"/>\n        <source>Idle</source>\n        <translation>Незайнятий</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"69\"/>\n        <source>Flags</source>\n        <translation>Прапорці</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"75\"/>\n        <source>Current Database</source>\n        <translation>Поточна база даних</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"10\"/>\n        <source>Add New Key to </source>\n        <translation>Додати новий ключ до </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"56\"/>\n        <location filename=\"../../qml/value-editor/editors/HashItemEditor.qml\" line=\"17\"/>\n        <source>Key:</source>\n        <translation>Ключ:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"66\"/>\n        <source>Type:</source>\n        <translation>Тип:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"107\"/>\n        <source>Or Import Value from the file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"113\"/>\n        <source>(Optional) Any file</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"115\"/>\n        <source>Select file with value</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"39\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"127\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"254\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"616\"/>\n        <source>Save</source>\n        <translation>Зберегти</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Edit Connections Group</source>\n        <translation>Редагувати групу підключень</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Add New Connections Group</source>\n        <translation>Додати нову групу підключень</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"29\"/>\n        <source>Group Name:</source>\n        <translation>Назва групи:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1091\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/editors/formatters/ValueFormatters.qml\" line=\"251\"/>\n        <source>Error</source>\n        <translation>Помилка</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"12\"/>\n        <source>Page</source>\n        <translation>Сторінка</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"68\"/>\n        <source>Enter valid value</source>\n        <translation>Введіть дійсне значення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"286\"/>\n        <source>Formatting error</source>\n        <translation>Помилка форматування</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"291\"/>\n        <source>Unknown formatter error (Empty response)</source>\n        <translation>Невідома помилка форматування (порожня відповідь)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"363\"/>\n        <source>[Binary]</source>\n        <translation>[Бінарне значення]</translation>\n    </message>\n    <message>\n        <source> [Compressed: </source>\n        <translation type=\"vanished\"> [Стиснутий: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"544\"/>\n        <source>Copy to Clipboard</source>\n        <translation>Копіювати в буфер обміну</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"600\"/>\n        <source>Exit </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"601\"/>\n        <source>Full Screen Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"617\"/>\n        <source>Save Changes</source>\n        <translation>Зберегти зміни</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"719\"/>\n        <source>Search string</source>\n        <translation>Шукати строку</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find Next</source>\n        <translation>Знайти далі</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find</source>\n        <translation>Знайти</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"764\"/>\n        <source>Regex</source>\n        <translation>Регулярний вираз</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"750\"/>\n        <source>Cannot find more results</source>\n        <translation>Не вдається знайти більше результатів</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"400\"/>\n        <source>Try to decompress:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"401\"/>\n        <source>Decompressed:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"491\"/>\n        <source>Cannot decompress value using </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"751\"/>\n        <source>Cannot find any results</source>\n        <translation>Не вдається знайти результати</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"899\"/>\n        <source>Binary value is too large to display</source>\n        <translation>Бінарне значення завелике для відображення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"366\"/>\n        <source>View as:</source>\n        <translation>Переглянути як:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"502\"/>\n        <source>Large value (&gt;150kB). Formatters are not available.</source>\n        <translation>Велике значення (&gt; 150 кБ). Форматери недоступні.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"18\"/>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"30\"/>\n        <source>Score</source>\n        <translation>Бал</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"10\"/>\n        <source>Bulk Operations Manager</source>\n        <translation>Менеджер масових операцій</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Invalid RDB path</source>\n        <translation>Недійсний шлях до RDB</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Please specify valid path to RDB file</source>\n        <translation>Вкажіть дійсний шлях до файлу RDB</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"88\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"89\"/>\n        <source>Delete keys</source>\n        <translation>Видалити ключі</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"98\"/>\n        <source>Set TTL</source>\n        <translation>Встановити TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"106\"/>\n        <source>Copy keys to another database</source>\n        <translation>Скопіювати ключі в іншу базу даних</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"107\"/>\n        <source>Copy keys</source>\n        <translation>Копіювати ключі</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"116\"/>\n        <source>Import data from rdb file</source>\n        <translation>Імпортувати дані з файлу rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"151\"/>\n        <source>Redis Server:</source>\n        <translation>Сервер Redis:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"163\"/>\n        <source>Database number:</source>\n        <translation>Номер бази даних:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"183\"/>\n        <source>Path to RDB file:</source>\n        <translation>Шлях до файлу RDB:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"194\"/>\n        <source>Path to dump.rdb file</source>\n        <translation>Шлях до файлу dump.rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"196\"/>\n        <source>Select dump.rdb</source>\n        <translation>Виберіть dump.rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"205\"/>\n        <source>Select DB in RDB file:</source>\n        <translation>Виберіть БД у файлі RDB:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Import keys that match &lt;b&gt;regex&lt;/b&gt;:</source>\n        <translation>Імпортувати ключі, що відповідають &lt;b&gt;регулярному виразу&lt;/b&gt;:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Key pattern:</source>\n        <translation>Шаблон ключів:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"272\"/>\n        <source>Destination Redis Server:</source>\n        <translation>Цільовий Redis Server:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"283\"/>\n        <source>Destination Redis Server Database Index:</source>\n        <translation>Індекс цільової бази даних сервера Redis:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show matched keys</source>\n        <translation>Показати ключі, які будуть змінені</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show Affected keys</source>\n        <translation>Показати ключі, які будуть змінені</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Matched keys:</source>\n        <translation>Знайдені ключі:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Affected keys:</source>\n        <translation>Ключі, що зазнають впливу:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"368\"/>\n        <source>Bulk Operation finished.</source>\n        <translation>Масова операція закінчена.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"373\"/>\n        <source>Bulk Operation finished with errors</source>\n        <translation>Масова операція виконана з помилками</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"425\"/>\n        <source>Processed: </source>\n        <translation>Оброблено: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"427\"/>\n        <source>Getting list of affected keys...</source>\n        <translation>Отримання списку ключів які будуть змінені...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"475\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1097\"/>\n        <source>Success</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"501\"/>\n        <source>Confirmation</source>\n        <translation>Підтвердження</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"502\"/>\n        <source>Do you really want to perform bulk operation?</source>\n        <translation>Ви дійсно хочете виконати масову операцію?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"18\"/>\n        <source>ID</source>\n        <translation>ID</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"61\"/>\n        <source>Value (represented as JSON object)</source>\n        <translation>Значення (представлене як об&apos;єкт JSON)</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"127\"/>\n        <source>The row has been changed on server.Reload and try again.</source>\n        <translation>Рядок змінено на сервері. Перезавантажте та повторіть спробу.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/bulkoperationsmanager.cpp\" line=\"131\"/>\n        <source>Failed to perform actions on %1 keys. </source>\n        <translation>Не вдалося виконати дії для %1 ключів. </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"12\"/>\n        <source>Cannot copy key </source>\n        <translation>Не вдається скопіювати ключ </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"123\"/>\n        <source>Source connection error</source>\n        <translation>Помилка підключення до серверу джерела</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"135\"/>\n        <source>Target connection error</source>\n        <translation>Помилка цільового підключення</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/deleteoperation.cpp\" line=\"11\"/>\n        <source>Cannot remove key </source>\n        <translation>Не вдається видалити ключ </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"17\"/>\n        <source>Cannot execute command </source>\n        <translation>Не вдається виконати команду </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"26\"/>\n        <source>Invalid regexp for keys filter.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"39\"/>\n        <source>Cannot get the list of affected keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/ttloperation.cpp\" line=\"11\"/>\n        <source>Cannot set TTL for key </source>\n        <translation>Не вдається встановити TTL для ключа </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/abstractnamespaceitem.cpp\" line=\"381\"/>\n        <source>Your redis-server doesn&apos;t support &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; commands.</source>\n        <translation>Ваш redis сервер не підтримує команду &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt;.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"138\"/>\n        <source>Key was added. Do you want to reload keys in selected namespace?</source>\n        <translation>Ключ додано. Хочете перезавантажити ключі в обраному неймспейсі?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/FilePathInput.qml\" line=\"27\"/>\n        <source>Select File</source>\n        <translation>Вибрати файл</translation>\n    </message>\n    <message>\n        <source>Save to File</source>\n        <translation type=\"vanished\">Зберегти у файл</translation>\n    </message>\n    <message>\n        <source>Save Value</source>\n        <translation type=\"vanished\">Зберегти значення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"913\"/>\n        <source>Save value to file</source>\n        <translation>Зберегти значення у файл</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Raw Value to File</source>\n        <translation>Зберегти необроблене значення у файл</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Formatted Value to File</source>\n        <translation>Зберегти відформатоване значення у файл</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Raw Value</source>\n        <translation>Зберегти необроблене значення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Formatted Value</source>\n        <translation>Зберегти відформатоване значення</translation>\n    </message>\n    <message>\n        <source>Save raw value to file</source>\n        <translation type=\"vanished\">Зберегти необроблене значення у файл</translation>\n    </message>\n    <message>\n        <source>Save formatted value to file</source>\n        <translation type=\"vanished\">Зберегти відформатоване значення у файл</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"51\"/>\n        <source>Value was saved to file:</source>\n        <translation>Значення було збережено у файл:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/abstractoperation.cpp\" line=\"38\"/>\n        <source>Cannot connect to redis-server</source>\n        <translation>Не вдається підключитися до redis-server</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"13\"/>\n        <source>Edit Connection Group</source>\n        <translation>Редагувати групу підключень</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"17\"/>\n        <source>Delete Connection Group</source>\n        <translation>Видалити групу підключень</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/servergroup.cpp\" line=\"58\"/>\n        <source>Do you really want to delete group &lt;b&gt;with all connections&lt;/b&gt;?</source>\n        <translation>Ви дійсно хочете видалити групу &lt;b&gt;з усіма підключеннями&lt;/b&gt;?</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"8\"/>\n        <source>Order of elements:</source>\n        <translation>Порядок елементів:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"20\"/>\n        <source>Default</source>\n        <translation>За замовчуванням</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"21\"/>\n        <source>Reverse</source>\n        <translation>Зворотний</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"28\"/>\n        <source>Start date should be less than End date</source>\n        <translation>Дата початку має бути меншою за дату завершення</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"136\"/>\n        <source>Apply filter</source>\n        <translation>Застосувати фільтр</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"147\"/>\n        <source>Network is not accessible. Please ensure that you have internet access and try again.</source>\n        <translation>Мережа недоступна. Переконайтесь, що у вас є доступ до Інтернету, і повторіть спробу.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"190\"/>\n        <source>Invalid login or password</source>\n        <translation>Недійсний логін або пароль</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"200\"/>\n        <source>Too many requests from your IP</source>\n        <translation>Забагато запитів з вашого IP</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"206\"/>\n        <source>Unknown error. Status code %1</source>\n        <translation>Невідома помилка. Код відповіді %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"321\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"734\"/>\n        <source>Cannot parse server reply</source>\n        <translation>Не вдається розібрати відповідь сервера</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"341\"/>\n        <source>Cannot validate token</source>\n        <translation>Не вдається перевірити токен</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"347\"/>\n        <source>Cannot login - %1. &lt;br/&gt; Please try again or contact  &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</source>\n        <translation type=\"unfinished\">Не вдається ввійти -%1. &lt;br/&gt; Спробуйте ще раз або зв’яжіться з &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"718\"/>\n        <source>Expired activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"731\"/>\n        <source>Invalid activation code</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"588\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"614\"/>\n        <source>Cannot save the update. Disk is full or download folder is not writable.</source>\n        <translation>Не вдається зберегти оновлення. Диск заповнений або папка для завантаження недоступна для запису.</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"666\"/>\n        <source>Download was canceled</source>\n        <translation>Завантаження скасовано</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"673\"/>\n        <source>Network error</source>\n        <translation>Помилка мережі</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"19\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"25\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"49\"/>\n        <source>Trial is active till</source>\n        <translation>Пробна версія активна до</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"58\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"59\"/>\n        <source>Licensed to</source>\n        <translation>Ліцензія зареєстрована на</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"66\"/>\n        <source>Subscription is active until:</source>\n        <translation>Підписка активна до:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Manage Subscription</source>\n        <translation>Керувати підпискою</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"30\"/>\n        <source>&lt;span style=&quot;font-size: 11px;&quot;&gt;Powered by awesome &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;open-source software&lt;/a&gt; and &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</source>\n        <translation>&lt;span style=&quot;font-size: 11px;&quot;&gt;Використовується крутезне &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;програмне забезпечення з відкритим кодом&lt;/a&gt; та &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"11\"/>\n        <source>Getting Started</source>\n        <translation>Початок роботи</translation>\n    </message>\n    <message>\n        <source>Thank you for choosing RDM. Let&apos;s make your Redis experience better.</source>\n        <translation type=\"vanished\">Дякуємо, що обрали RDM. Давайте зробимо вашу роботу з Redis краще.</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"42\"/>\n        <source>Thank you for choosing RESP.app. Let&apos;s make your Redis experience better.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"60\"/>\n        <source>Connect to Redis-Server</source>\n        <translation>Підключитися до Redis-Server</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"73\"/>\n        <source>Read the Docs</source>\n        <translation>Читати документацію</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/loadmoreitem.cpp\" line=\"12\"/>\n        <source>Load more keys</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"19\"/>\n        <source>SSH Passphrase</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"21\"/>\n        <source>Unknown</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <source>Passphrase</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"71\"/>\n        <source>Continue</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"24\"/>\n        <source>Yes</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"32\"/>\n        <source>No</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/ColorInput.qml\" line=\"43\"/>\n        <source>Select</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/UnsupportedDataType.qml\" line=\"24\"/>\n        <source>Unsupported Redis Data type </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"163\"/>\n        <source>Cannot delete key:\n\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "src/resources/translations/rdm_zh_CN.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n    <name>QObject</name>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"360\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"397\"/>\n        <source>Cannot connect to cluster node %1:%2</source>\n        <translation>无法连接集群节点 %1:%2</translation>\n    </message>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"408\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"435\"/>\n        <source>Cannot flush db (%1): %2</source>\n        <translation>无法刷新库 (%1): %2</translation>\n    </message>\n</context>\n<context>\n    <name>RESP</name>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"82\"/>\n        <source>Settings directory is not writable</source>\n        <translation>设置保存文件夹没有写入权限</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"84\"/>\n        <source>RESP.app can&apos;t save connections file to settings directory. Please change file permissions or restart RESP.app as administrator.</source>\n        <translation>RESP.app 无法将连接文件保存到设置目录。 请更改文件权限或以管理员身份重新启动 RESP.app。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"81\"/>\n        <source>Cannot rename key %1: %2</source>\n        <translation>无法重命名键 %1: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"125\"/>\n        <source>Cannot persist key &apos;%1&apos;. &lt;br&gt; Key does not exist or does not have an assigned TTL value</source>\n        <translation>无法持久化键 &apos;%1&apos;，&lt;br&gt; 键不存在或没有设置TTL时长</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"160\"/>\n        <source>Cannot parse scan response</source>\n        <translation>无法解析扫描结果</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"328\"/>\n        <source>Server returned unexpected response: </source>\n        <translation>服务器返回了意外结果：</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"106\"/>\n        <source>Cannot set TTL for key %1</source>\n        <translation>无法给键 %1 设置 TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"274\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"285\"/>\n        <source>Cannot load rows for key %1: %2</source>\n        <translation>无法加载键内容 %1: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"42\"/>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"75\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"14\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"41\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"12\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"33\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"44\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"77\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"48\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"59\"/>\n        <source>Invalid row</source>\n        <translation>无效行</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"113\"/>\n        <source>Value with the same key already exists</source>\n        <translation>同名键值已经存在</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"184\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"340\"/>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"151\"/>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"84\"/>\n        <source>Connection error: </source>\n        <translation>连接错误：</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"136\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"136\"/>\n        <source>Data was loaded from server partially.</source>\n        <translation>部分数据已经从服务器加载。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"26\"/>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"38\"/>\n        <source>Cannot load key %1, connection error occurred: %2</source>\n        <translation>无法加载键 %1，连接发生错误：%2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"49\"/>\n        <source>Cannot load key %1 because it doesn&apos;t exist in database. Please reload connection tree and try again.</source>\n        <translation>无法加载键 %1，数据库中不存在该键，请重载连接树后重试。</translation>\n    </message>\n    <message>\n        <source>Cannot load TTL for key %1, connection error occurred: %2</source>\n        <translation type=\"vanished\">无法加载键 %1 的 TTL 值，连接发生错误: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"78\"/>\n        <source>Cannot retrieve type of the key: </source>\n        <translation>无法重设键类型：</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"122\"/>\n        <source>Cannot open file with key value</source>\n        <translation>无法用键值打开文件</translation>\n    </message>\n    <message>\n        <source>Unsupported Redis Data type %1</source>\n        <translation type=\"vanished\">数据格式不支持 %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"129\"/>\n        <source>Cannot connect to server &apos;%1&apos;. Check log for details.</source>\n        <translation>无法连接到服务器 &apos;%1&apos;，详情请查看日志。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"139\"/>\n        <source>Open Source version of RESP.app &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation>RESP.app 的开源版本&lt;b&gt;不支持 SSH 隧道&lt;/b&gt;。&lt;br /&gt;&lt;br /&gt; 要获得功能齐全的应用程序，请在 &lt;a href=&apos;https://resp 上购买订阅 .app/subscriptions&apos;&gt;resp.app&lt;/a&gt;。 &lt;br/&gt;&lt;br /&gt;每一次订阅都为我们提供了资金来继续开发过程并为我们的用户提供支持。 &lt;br /&gt;如果您有任何问题，请随时通过 &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; 与我们联系或加入 &lt;a href=&apos;https:// t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;。 </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"229\"/>\n        <source>Cannot load keys: %1</source>\n        <translation>无法加载键：%1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"336\"/>\n        <source>Delete key error: %1</source>\n        <translation>删除键失败: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"477\"/>\n        <source>Cannot determine amount of used memory by key: %1</source>\n        <translation>无法调用该键占用的内存: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"416\"/>\n        <source>Cannot flush database: </source>\n        <translation>清空库错误:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/common/tabmodel.cpp\" line=\"43\"/>\n        <source>Invalid Connection. Check connection settings.</source>\n        <translation>无效连接，请检查连接设置。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"251\"/>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carefully or change limit in settings.</source>\n        <translation>由于超出加载键数量限制，实时更新功能已经关闭。请设置更精确的筛查条件或更改加载限制设定。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"304\"/>\n        <source>Key was added. Do you want to reload keys in selected database?</source>\n        <translation>键已经添加。需要重新加载该数据库的键名吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"312\"/>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"143\"/>\n        <source>Key was added</source>\n        <translation>键已经插入</translation>\n    </message>\n    <message>\n        <source>Another operation is currently in progress</source>\n        <translation type=\"vanished\">另一个操作正在进行中</translation>\n    </message>\n    <message>\n        <source>Please wait until another operation will be finished.</source>\n        <translation type=\"vanished\">请耐心等待另一个操作完成。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"327\"/>\n        <source>Do you really want to remove all keys from this database?</source>\n        <translation>确定要删除该数据库里面所有的键吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"73\"/>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"75\"/>\n        <source>Cannot load databases:\n\n</source>\n        <translation>无法加载数据库：\n\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"250\"/>\n        <source>Live update was disabled</source>\n        <translation>实时更新已关闭</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"190\"/>\n        <source>Rename key</source>\n        <translation>重命名键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"199\"/>\n        <source>New name:</source>\n        <translation>新名称：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"21\"/>\n        <source>Total pages: </source>\n        <translation>总页数: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"45\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"222\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"358\"/>\n        <source>Size: </source>\n        <translation>大小：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"228\"/>\n        <source>TTL:</source>\n        <translation>TTL：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"233\"/>\n        <source>Set key TTL</source>\n        <translation>设置键的 TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"242\"/>\n        <source>New TTL:</source>\n        <translation>新的 TTL：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"298\"/>\n        <source>Delete</source>\n        <translation>删除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"23\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"303\"/>\n        <source>Delete key</source>\n        <translation>删除键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"38\"/>\n        <source>Changes are not saved</source>\n        <translation>更改未保存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"39\"/>\n        <source>Do you want to close key tab without saving changes?</source>\n        <translation>不保存更改关闭标签页吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"260\"/>\n        <source>Persist key</source>\n        <translation>持久化键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"304\"/>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"153\"/>\n        <source>Do you really want to delete this key?</source>\n        <translation>确定要删除该键？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"140\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"318\"/>\n        <source>Reload Value</source>\n        <translation>重载键值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"22\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"31\"/>\n        <source>Add Row</source>\n        <translation>插入行</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"30\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"526\"/>\n        <source>Add Element to HLL</source>\n        <translation>添加元素到HLL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"68\"/>\n        <source>Add</source>\n        <translation>添加</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"101\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"122\"/>\n        <source>Delete row</source>\n        <translation>删除行</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"107\"/>\n        <source>The row is the last one in the key. After removing it key will be deleted.</source>\n        <translation>此行数据是该键最后一行数据。删除此行数据，该键将会被删除。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"109\"/>\n        <source>Do you really want to remove this row?</source>\n        <translation>确定要删除该行数据吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"164\"/>\n        <source>Search on page...</source>\n        <translation>页面搜索中...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"191\"/>\n        <source>Full Search</source>\n        <translation>全文搜索</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"191\"/>\n        <source>Value and Console tabs related to this connection will be closed. Do you want to continue?</source>\n        <translation>所有与该连接相关的键值对话框和命令操作对话框都将被关闭，确定要继续吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"204\"/>\n        <source>Do you really want to delete connection?</source>\n        <translation>确定要删除连接？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"13\"/>\n        <source>Connected to cluster.\n</source>\n        <translation>已连接到集群。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"16\"/>\n        <source>Connected.\n</source>\n        <translation>已连接。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"60\"/>\n        <source>Switch to %1 mode. Close console tab to stop listen for messages.</source>\n        <translation>切换到推送/订阅模式，关闭标签页来停止接收信息。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"69\"/>\n        <source>Subscribe error: %1</source>\n        <translation>订阅错误：%1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/server-actions/serverstatsmodel.cpp\" line=\"36\"/>\n        <source>Server %0</source>\n        <translation>服务器 %0</translation>\n    </message>\n    <message>\n        <source>Can&apos;t find formatter with name: %1</source>\n        <translation type=\"vanished\">找不到格式化配置名：%1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"109\"/>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"147\"/>\n        <source>Can&apos;t find formatter: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"115\"/>\n        <source>Invalid callback</source>\n        <translation>无效回调</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"204\"/>\n        <source>Can&apos;t load list of available formatters from extension server: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"260\"/>\n        <source>Can&apos;t encode value: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Cannot decode value using %1 formatter. </source>\n        <translation type=\"vanished\">无法使用 %1 格式化配置来解析值。</translation>\n    </message>\n    <message>\n        <source>Cannot validate value using %1 formatter.</source>\n        <translation type=\"vanished\">无法使用 %1 格式化配置来效验值。</translation>\n    </message>\n    <message>\n        <source>Cannot encode value using %1 formatter. </source>\n        <translation type=\"vanished\">无法使用 %1 编码键值</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"26\"/>\n        <source>Loading key: %1 from db %2</source>\n        <translation>从 db %2 加载 key: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"68\"/>\n        <source>Cannot open value tab</source>\n        <translation>无法打开键值对话框</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"97\"/>\n        <source>Connection error</source>\n        <translation>连接错误</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"115\"/>\n        <source>Connection error. Can&apos;t open value tab. </source>\n        <translation>连接错误，无法打开键值对话框。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"176\"/>\n        <source>Cannot reload key value: %1</source>\n        <translation>无法重载键值: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"228\"/>\n        <source>Cannot load key value: %1</source>\n        <translation>无法加载键值：%1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"29\"/>\n        <source>Connect to Redis Server</source>\n        <translation>连接到 Redis 服务器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"117\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"205\"/>\n        <source>Import</source>\n        <translation>导入</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"50\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"69\"/>\n        <source>Import Connections</source>\n        <translation>导入连接</translation>\n    </message>\n    <message>\n        <source>Export</source>\n        <translation type=\"obsolete\">导出</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"58\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"74\"/>\n        <source>Export Connections</source>\n        <translation>导出连接</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"100\"/>\n        <source>Report issue</source>\n        <translation>报告错误</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"107\"/>\n        <source>Documentation</source>\n        <translation>文档</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"114\"/>\n        <source>Join Telegram Chat</source>\n        <translation>加入 Telegram 聊天组</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"121\"/>\n        <source>Follow</source>\n        <translation>关注</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"128\"/>\n        <source>Star on GitHub!</source>\n        <translation>给我们的GitHub加个星星吧！</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"136\"/>\n        <source>Log</source>\n        <translation>日志</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"144\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"13\"/>\n        <source>Extension Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"154\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"13\"/>\n        <source>Settings</source>\n        <translation>设置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>New Connection Settings</source>\n        <translation>新连接设置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"144\"/>\n        <source>How to connect</source>\n        <translation>怎么连接</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"151\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"48\"/>\n        <source>Connection Settings</source>\n        <translation>连接设置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"180\"/>\n        <source>Create connection from Redis URL</source>\n        <translation>从Redis URL创建连接</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"233\"/>\n        <source>Learn more about Redis URL:  </source>\n        <translation>认识Redis URL: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"240\"/>\n        <source>Connection guides</source>\n        <translation>连接向导</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"260\"/>\n        <source>Local or Public Redis</source>\n        <translation>本地或对外的Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"276\"/>\n        <source>Redis with SSL/TLS</source>\n        <translation>使用SSL/TLS的Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"292\"/>\n        <source>SSH tunnel</source>\n        <translation>SSH通道</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"308\"/>\n        <source>UNIX socket</source>\n        <translation>UNIX套接字</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"395\"/>\n        <source>Cannot figure out how to connect to your redis-server?</source>\n        <translation>不知道怎么连接到您的Redis服务端吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"401\"/>\n        <source>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Read the Docs&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;Contact Support&lt;/a&gt; or ask for help in our &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram Group&lt;/a&gt;</source>\n        <translation type=\"unfinished\">&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;查看文档&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;联系支持&lt;/a&gt; 或者点这里寻求帮助 &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram Group&lt;/a&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"413\"/>\n        <source>Don&apos;t have running Redis?</source>\n        <translation>Redis没有启动吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"419\"/>\n        <source>Spin up hassle-free Redis on Digital Ocean</source>\n        <translation>快速使用Digital Ocean上的Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"431\"/>\n        <source>Skip</source>\n        <translation>跳过</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"466\"/>\n        <source>Name:</source>\n        <translation>名字：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"472\"/>\n        <source>Connection Name</source>\n        <translation>连接名</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"478\"/>\n        <source>Address:</source>\n        <translation>地址</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"483\"/>\n        <source>redis-server host</source>\n        <translation>Redis 服务器地址</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"507\"/>\n        <source>(Optional) redis-server authentication password</source>\n        <translation>(可选) Redis 服务器验证密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"526\"/>\n        <source>Security</source>\n        <translation>安全</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"573\"/>\n        <source>Public Key:</source>\n        <translation>公钥：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"579\"/>\n        <source>(Optional) Public Key in PEM format</source>\n        <translation>(可选) PEM 格式公钥</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"581\"/>\n        <source>Select public key in PEM format</source>\n        <translation>选择 PEM 格式公钥</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"592\"/>\n        <source>(Optional) Private Key in PEM format</source>\n        <translation>(可选) PEM 格式私钥</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"594\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"703\"/>\n        <source>Select private key in PEM format</source>\n        <translation>选择 PEM 格式私钥</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"599\"/>\n        <source>Authority:</source>\n        <translation>授权：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"605\"/>\n        <source>(Optional) Authority in PEM format</source>\n        <translation>(可选) PEM 格式授权</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"607\"/>\n        <source>Select authority file in PEM format</source>\n        <translation>选择 PEM 格式授权文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"553\"/>\n        <source>SSH Tunnel</source>\n        <translation>SSH 通道</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"630\"/>\n        <source>SSH Address:</source>\n        <translation>SSH 地址：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"634\"/>\n        <source>Remote Host with SSH server</source>\n        <translation>SSH 远程服务器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"642\"/>\n        <source>SSH User:</source>\n        <translation>SSH 用户：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"648\"/>\n        <source>Valid SSH User Name</source>\n        <translation>验证 SSH 用户名</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"586\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"683\"/>\n        <source>Private Key</source>\n        <translation>私钥</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"701\"/>\n        <source>Path to Private Key in PEM format</source>\n        <translation>PEM 格式私钥路径</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"711\"/>\n        <source>&lt;b&gt;Tip:&lt;/b&gt; Use &lt;code&gt;⌘ + Shift + .&lt;/code&gt; to show hidden files and folders in dialog</source>\n        <translation>&lt;b&gt;提示：&lt;/b&gt; 使用 &lt;code&gt;⌘ + Shift + .&lt;/code&gt; 在对话框中显示隐藏文件和文件夹</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"87\"/>\n        <source>Password</source>\n        <translation>密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"737\"/>\n        <source>SSH User Password</source>\n        <translation>SSH 用户密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"759\"/>\n        <source>Enable TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</source>\n        <translation>启用 TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"156\"/>\n        <source>Advanced Settings</source>\n        <translation>高级设置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"494\"/>\n        <source>For better network performance please use 127.0.0.1</source>\n        <translation>请使用127.0.0.1获得更好的网络连接速度</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"512\"/>\n        <source>Username:</source>\n        <translation>用户名:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"518\"/>\n        <source>(Optional) redis-server authentication username (Redis &gt;6.0)</source>\n        <translation>可选：服务端认证用户名 (Redis &gt;6.0)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"540\"/>\n        <source>SSL / TLS</source>\n        <translation>SSL / TLS</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"612\"/>\n        <source>Enable strict mode:</source>\n        <translation>打开严格模式: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"657\"/>\n        <source>Use SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"667\"/>\n        <source>(Optional) Custom SSH Agent Path</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"669\"/>\n        <source>Select SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"678\"/>\n        <source>Additional configuration is required to enable SSH Agent support</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"734\"/>\n        <source>Passphrase for provided private key</source>\n        <translation>提供的私钥的密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"736\"/>\n        <source>Password request will be prompt prior to connection</source>\n        <translation>将会在连接前询问密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"747\"/>\n        <source>Ask for password</source>\n        <translation>询问密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"796\"/>\n        <source>Keys loading</source>\n        <translation>键加载</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"800\"/>\n        <source>Default filter:</source>\n        <translation>默认过滤：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"806\"/>\n        <source>Pattern which defines loaded keys from redis-server</source>\n        <translation>指定加载键名表达式：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"812\"/>\n        <source>Namespace Separator:</source>\n        <translation>命名空间分隔符：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"819\"/>\n        <source>Separator used for namespace extraction from keys</source>\n        <translation>键名中命名空间分隔符</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"825\"/>\n        <source>Timeouts &amp; Limits</source>\n        <translation>设置超时和限制</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"829\"/>\n        <source>Connection Timeout (sec):</source>\n        <translation>连接超时 (秒)：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"842\"/>\n        <source>Execution Timeout (sec):</source>\n        <translation>执行超时 (秒)：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"853\"/>\n        <source>Databases discovery limit:</source>\n        <translation>数据库发现限制：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"867\"/>\n        <source>Cluster</source>\n        <translation>集群</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"871\"/>\n        <source>Change host on cluster redirects:</source>\n        <translation>修改集群重定向：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"881\"/>\n        <source>Formatters</source>\n        <translation>格式化程序</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"885\"/>\n        <source>Default value formatter:</source>\n        <translation>默认值格式化程序：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"901\"/>\n        <source>Auto detect (JSON / Plain Text / HEX)</source>\n        <translation>自动检测（JSON / 纯文本 / HEX）</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"902\"/>\n        <source>Last selected</source>\n        <translation>最后选择</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"903\"/>\n        <source>Select formatter ...</source>\n        <translation>选择格式化程序...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"952\"/>\n        <source>Appearance</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"956\"/>\n        <source>Icon color:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1021\"/>\n        <source>Invalid settings detected!</source>\n        <translation>检测到无效的设置！</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"992\"/>\n        <source>Test Connection</source>\n        <translation>测试连接</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/OkDialogOverlay.qml\" line=\"20\"/>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"111\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1029\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"163\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"319\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"903\"/>\n        <source>OK</source>\n        <translation>确定</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"294\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"508\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"402\"/>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"44\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"61\"/>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"89\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1057\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"175\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"331\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"172\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"89\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"268\"/>\n        <source>Cancel</source>\n        <translation>取消</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"48\"/>\n        <source>General</source>\n        <translation>通用</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"53\"/>\n        <source>Application will be restarted to apply these settings.</source>\n        <translation>重启软件来启用新的设置。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"73\"/>\n        <source>Language</source>\n        <translation>语言</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"393\"/>\n        <source>Application will be restarted to apply this setting.</source>\n        <translation>新设置将在软件重启后生效</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"85\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"174\"/>\n        <source>Font</source>\n        <translation>字体</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"97\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"186\"/>\n        <source>Font Size</source>\n        <translation>字体大小</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"110\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"124\"/>\n        <source>Dark Mode</source>\n        <translation>暗色模式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"200\"/>\n        <source>Maximum Formatted Value Size</source>\n        <translation>最大格式化长度</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"201\"/>\n        <source>Size in bytes</source>\n        <translation>字节长度</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"259\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"392\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"138\"/>\n        <source>Use system proxy settings</source>\n        <translation>使用系统代理设置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"150\"/>\n        <source>Use system proxy only for HTTP(S) requests</source>\n        <translation>只对HTTP(S)请求使用系统代理设置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"156\"/>\n        <source>Value Editor</source>\n        <translation>内容编辑器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"213\"/>\n        <source>Maximum amount of items per page</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"218\"/>\n        <source>Connections Tree</source>\n        <translation>连接树</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"236\"/>\n        <source>Show namespaced keys on top</source>\n        <translation>在头部展示命名空间键名</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"246\"/>\n        <source>Reopen namespaces on reload</source>\n        <translation>重载时重新打开命名空间</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"247\"/>\n        <source>(Disable to improve treeview performance)</source>\n        <translation>(禁用树状视图提高性能)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"258\"/>\n        <source>Show only last part for namespaced keys</source>\n        <translation>仅显示命名空间中键名最后一部分内容</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"270\"/>\n        <source>Limit for SCAN command</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"282\"/>\n        <source>Maximum amount of rendered child items</source>\n        <translation>子项目的最大渲染数量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"294\"/>\n        <source>Live update maximum allowed keys</source>\n        <translation>实时更新最大允许键数量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"306\"/>\n        <source>Live update interval (in seconds)</source>\n        <translation>实时更新间隔 (秒)</translation>\n    </message>\n    <message>\n        <source>External Value View Formatters</source>\n        <translation type=\"vanished\">外部键值格式化配置</translation>\n    </message>\n    <message>\n        <source>Formatters path: %0</source>\n        <translation type=\"vanished\">格式化配置路径：%0</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"61\"/>\n        <source>Server Url:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"75\"/>\n        <source>Basic Auth:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"81\"/>\n        <source>User</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"99\"/>\n        <source>Response timeout  (in seconds)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"109\"/>\n        <source>Available Data Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"117\"/>\n        <source>Reload</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"135\"/>\n        <source>Id</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"141\"/>\n        <source>Name</source>\n        <translation>名称</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"147\"/>\n        <source>Read Only</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"29\"/>\n        <source>Version</source>\n        <translation>版本</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1001\"/>\n        <source>Quick Start Guide</source>\n        <translation>快速入门指南</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"134\"/>\n        <source>Successful connection to redis-server</source>\n        <translation>连接 Redis 服务器成功</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"137\"/>\n        <source>Can&apos;t connect to redis-server</source>\n        <translation>无法连接 Redis 服务器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"321\"/>\n        <source>Add Group</source>\n        <translation>添加组</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"336\"/>\n        <source>Regroup connections</source>\n        <translation>重组连接</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"358\"/>\n        <source>Exit Regroup Mode</source>\n        <translation>退出重组模式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"10\"/>\n        <source>Bulk Operations Manager</source>\n        <translation>批量操作管理</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Invalid RDB path</source>\n        <translation>无效的RDB路径</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Please specify valid path to RDB file</source>\n        <translation>请指定有效的RDB文件路径</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"88\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"89\"/>\n        <source>Delete keys</source>\n        <translation>删除键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"98\"/>\n        <source>Set TTL</source>\n        <translation>设置TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"106\"/>\n        <source>Copy keys to another database</source>\n        <translation>复制键到其他库</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"107\"/>\n        <source>Copy keys</source>\n        <translation>复制键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"116\"/>\n        <source>Import data from rdb file</source>\n        <translation>从RDB文件导入数据</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"151\"/>\n        <source>Redis Server:</source>\n        <translation>Redis 服务器：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"163\"/>\n        <source>Database number:</source>\n        <translation>数据库编号：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"183\"/>\n        <source>Path to RDB file:</source>\n        <translation>RDB文件路径：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"205\"/>\n        <source>Select DB in RDB file:</source>\n        <translation>从RDB文件选择库：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Key pattern:</source>\n        <translation>键名表达式：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Import keys that match &lt;b&gt;regex&lt;/b&gt;:</source>\n        <translation>导入匹配&lt;b&gt;regex&lt;/b&gt;的键：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"272\"/>\n        <source>Destination Redis Server:</source>\n        <translation>目标 Redis 服务器：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"283\"/>\n        <source>Destination Redis Server Database Index:</source>\n        <translation>目标 Redis 数据库编号：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show matched keys</source>\n        <translation>显示匹配的键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show Affected keys</source>\n        <translation>显示受影响的键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Affected keys:</source>\n        <translation>受影响的键：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Matched keys:</source>\n        <translation>匹配的键：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"368\"/>\n        <source>Bulk Operation finished.</source>\n        <translation>批量操作完成。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"373\"/>\n        <source>Bulk Operation finished with errors</source>\n        <translation>批量操作完成，但发生了一些错误。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"425\"/>\n        <source>Processed: </source>\n        <translation>已处理：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"427\"/>\n        <source>Getting list of affected keys...</source>\n        <translation>获取受影响的键列表...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"475\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1097\"/>\n        <source>Success</source>\n        <translation>成功</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"501\"/>\n        <source>Confirmation</source>\n        <translation>确认</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"502\"/>\n        <source>Do you really want to perform bulk operation?</source>\n        <translation>确认要执行批量操作？</translation>\n    </message>\n    <message>\n        <source>Sign in with resp.app account</source>\n        <translation type=\"obsolete\">使用 resp.app 账号登陆</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"231\"/>\n        <source>Renew your subscription</source>\n        <translation>更新订阅</translation>\n    </message>\n    <message>\n        <source>Your trial has ended.</source>\n        <translation type=\"obsolete\">试用版已结束</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"234\"/>\n        <source>You have no active subscription</source>\n        <translation>您没有可用订阅</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"237\"/>\n        <source>No internet connection</source>\n        <translation>无网络</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"240\"/>\n        <source>Your trial has ended</source>\n        <translation>您的试用已结束</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"252\"/>\n        <source>To use this version you need to renew your subscription.</source>\n        <translation>要继续使用该版本，需要更新你的订阅。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"257\"/>\n        <source>If you’re behind a proxy please enable </source>\n        <translation>如果您处于代理之中，请启用代理</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"261\"/>\n        <source> option before sign-in.</source>\n        <translation> 登陆前选项</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"11\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"317\"/>\n        <source>Sign in with RESP.app account</source>\n        <translation>使用 RESP.app 帐户登录</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"255\"/>\n        <source>Please make sure that RESP.app is not blocked by a firewall and you have an internet connection.</source>\n        <translation>请确保 RESP.app 未被防火墙阻止并且您有互联网连接。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"264\"/>\n        <source>Please purchase a subscription to continue using RESP.app.</source>\n        <translation>请购买订阅以继续使用 RESP.app。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"268\"/>\n        <source>If you have any questions please contact support </source>\n        <translation>遇到任何问题，请联系支持。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"279\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Renew Subscription</source>\n        <translation>更新订阅</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"280\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"32\"/>\n        <source>Buy Subscription</source>\n        <translation>购买订阅</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"293\"/>\n        <source>Try Again</source>\n        <translation>重试</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"331\"/>\n        <source>Email:</source>\n        <translation>邮箱：</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"347\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"501\"/>\n        <source>Password:</source>\n        <translation>密码：</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"370\"/>\n        <location filename=\"../../qml/common/PasswordInput.qml\" line=\"29\"/>\n        <source>Show password</source>\n        <translation>显示密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"376\"/>\n        <source>Forgot password?</source>\n        <translation>忘记密码？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"404\"/>\n        <source>Sign In</source>\n        <translation>登录</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"409\"/>\n        <source>Please enter email &amp; password to sign in.</source>\n        <translation>请输入邮箱和密码来登录。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"422\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"444\"/>\n        <source>Offline Activation</source>\n        <translation>离线激活</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"465\"/>\n        <source>Paste Activation code here</source>\n        <translation>在这里粘贴激活码</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"481\"/>\n        <source>Where can I find my activation code?</source>\n        <translation>我在哪里能找到我的激活码？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"490\"/>\n        <source>Activate</source>\n        <translation>激活</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"495\"/>\n        <source>Please enter valid activation code.</source>\n        <translation>请输入正确的激活码。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/TreeItemDelegate.qml\" line=\"220\"/>\n        <source> (Removed)</source>\n        <translation> （删除）</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"77\"/>\n        <source>Open Keys Filter</source>\n        <translation>打开键过滤器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"81\"/>\n        <source>Reload Keys in Database</source>\n        <translation>重载该数据库的键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"85\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"30\"/>\n        <source>Add New Key</source>\n        <translation>添加新键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Disable Live Update</source>\n        <translation>关闭实时更新</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Enable Live Update</source>\n        <translation>打开实时更新</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"95\"/>\n        <source>Open Console</source>\n        <translation>打开控制台</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"98\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"32\"/>\n        <source>Analyze Used Memory</source>\n        <translation>分析内存占用</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"100\"/>\n        <source>Bulk Operations</source>\n        <translation>批量操作</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"121\"/>\n        <source>Flush Database</source>\n        <translation>清空数据库</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"124\"/>\n        <source>Delete keys with filter</source>\n        <translation>使用过滤器删除键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"97\"/>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"127\"/>\n        <source>Set TTL for multiple keys</source>\n        <translation>设置多个键的TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"130\"/>\n        <source>Copy keys from this database to another</source>\n        <translation>从本库复制键到其他库</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"133\"/>\n        <source>Import keys from RDB file</source>\n        <translation>从RDB文件导入键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"136\"/>\n        <source>Back</source>\n        <translation>返回</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"22\"/>\n        <source>Copy Key Name</source>\n        <translation>复制键值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"29\"/>\n        <source>Reload Namespace</source>\n        <translation>重载命名空间</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"31\"/>\n        <source>Copy Namespace Pattern</source>\n        <translation>复制命名空间模式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"33\"/>\n        <source>Delete Namespace</source>\n        <translation>删除命名空间</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"71\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"24\"/>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"14\"/>\n        <source>Disconnect</source>\n        <translation>断开连接</translation>\n    </message>\n    <message>\n        <source>Server Info</source>\n        <translation type=\"vanished\">服务器信息</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"20\"/>\n        <source>Reload Server</source>\n        <translation>重载服务器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"24\"/>\n        <source>Unload All Data</source>\n        <translation>卸载所有数据</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"28\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>Edit Connection Settings</source>\n        <translation>编辑连接设置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"32\"/>\n        <source>Duplicate Connection</source>\n        <translation>复制连接</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"36\"/>\n        <source>Delete Connection</source>\n        <translation>删除连接</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"21\"/>\n        <source>Connecting...</source>\n        <translation>连接中...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"193\"/>\n        <source>Clear</source>\n        <translation>清除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"234\"/>\n        <source>Arguments</source>\n        <translation>参数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"240\"/>\n        <source>Description</source>\n        <translation>描述</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"246\"/>\n        <source>Available since</source>\n        <translation>可用自</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"297\"/>\n        <source>Close</source>\n        <translation>关闭</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"108\"/>\n        <source>View Server Info</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"130\"/>\n        <source>Redis Version</source>\n        <translation>Redis 版本</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"169\"/>\n        <source>Used memory</source>\n        <translation>已使用的内存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"182\"/>\n        <source>Cmd Processed</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"203\"/>\n        <source>Monitor Commands</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"242\"/>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"319\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"105\"/>\n        <source>Clients</source>\n        <translation>客户端</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"377\"/>\n        <source>Server Actions</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Commands Processed</source>\n        <translation type=\"vanished\">已执行的命令</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"143\"/>\n        <source>Uptime</source>\n        <translation>运行时间</translation>\n    </message>\n    <message>\n        <source>Total Keys</source>\n        <translation type=\"vanished\">键总量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"156\"/>\n        <source>Hit Ratio</source>\n        <translation>命中率</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"262\"/>\n        <source>Server Stats</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"281\"/>\n        <source>Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"321\"/>\n        <source> day(s)</source>\n        <translation> 天</translation>\n    </message>\n    <message>\n        <source>Info</source>\n        <translation type=\"vanished\">信息</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"53\"/>\n        <source>Commands Per Second</source>\n        <translation>每秒执行命令数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"67\"/>\n        <source>Ops/s</source>\n        <translation>每秒查询率</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"91\"/>\n        <source>Connected Clients</source>\n        <translation>已连接的客户端数量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"129\"/>\n        <source>Memory Usage</source>\n        <translation>内存占用</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"141\"/>\n        <source>Mb</source>\n        <translation>Mb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"170\"/>\n        <source>Network Input</source>\n        <translation>网络输入</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"182\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"218\"/>\n        <source>Kb/s</source>\n        <translation>Kb/s</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"206\"/>\n        <source>Network Output</source>\n        <translation>网络输出</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"242\"/>\n        <source>Total Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"256\"/>\n        <source>Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Keys</source>\n        <translation type=\"vanished\">键数量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"36\"/>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"28\"/>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"39\"/>\n        <source>Auto Refresh</source>\n        <translation>自动刷新</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"66\"/>\n        <source>Property</source>\n        <translation>属性</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"72\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"22\"/>\n        <source>Value</source>\n        <translation>键值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"68\"/>\n        <source>Subscribe in Console</source>\n        <translation>控制台订阅</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"221\"/>\n        <source>Slowlog</source>\n        <translation>慢查询日志</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"299\"/>\n        <source>Pub/Sub Channels</source>\n        <translation>推送/订阅 通道</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"38\"/>\n        <source>Enable</source>\n        <translation>启用</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"57\"/>\n        <source>Channel Name</source>\n        <translation>通道名</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"54\"/>\n        <source>Command</source>\n        <translation>指令</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"71\"/>\n        <source>Processed at</source>\n        <translation>处理于</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"86\"/>\n        <source>Execution Time (μs)</source>\n        <translation>执行时长 (μs)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"51\"/>\n        <source>Client Address</source>\n        <translation>客户端地址</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"57\"/>\n        <source>Age (sec)</source>\n        <translation>时长 (sec)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"63\"/>\n        <source>Idle</source>\n        <translation>空闲</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"69\"/>\n        <source>Flags</source>\n        <translation>标记</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"75\"/>\n        <source>Current Database</source>\n        <translation>当前库</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"10\"/>\n        <source>Add New Key to </source>\n        <translation>添加新键到 </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"56\"/>\n        <location filename=\"../../qml/value-editor/editors/HashItemEditor.qml\" line=\"17\"/>\n        <source>Key:</source>\n        <translation>键名：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"66\"/>\n        <source>Type:</source>\n        <translation>类型：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"107\"/>\n        <source>Or Import Value from the file</source>\n        <translation>或从文件中导入值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"113\"/>\n        <source>(Optional) Any file</source>\n        <translation>(可选)任意文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"115\"/>\n        <source>Select file with value</source>\n        <translation>使用值选择文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"39\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"127\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"254\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"616\"/>\n        <source>Save</source>\n        <translation>保存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Edit Connections Group</source>\n        <translation>编辑连接组</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Add New Connections Group</source>\n        <translation>添加连接组</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"29\"/>\n        <source>Group Name:</source>\n        <translation>组名:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1091\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/editors/formatters/ValueFormatters.qml\" line=\"251\"/>\n        <source>Error</source>\n        <translation>错误</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"12\"/>\n        <source>Page</source>\n        <translation>页</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"68\"/>\n        <source>Enter valid value</source>\n        <translation>请输入有效的值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"286\"/>\n        <source>Formatting error</source>\n        <translation>格式化错误</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"291\"/>\n        <source>Unknown formatter error (Empty response)</source>\n        <translation>未知的格式化错误（无响应）</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"363\"/>\n        <source>[Binary]</source>\n        <translation>[二进制]</translation>\n    </message>\n    <message>\n        <source> [Compressed: </source>\n        <translation type=\"obsolete\">[压缩的：</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"544\"/>\n        <source>Copy to Clipboard</source>\n        <translation>复制到剪切板</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"600\"/>\n        <source>Exit </source>\n        <translation>退出</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"601\"/>\n        <source>Full Screen Mode</source>\n        <translation>全屏模式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"617\"/>\n        <source>Save Changes</source>\n        <translation>保存更改</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"719\"/>\n        <source>Search string</source>\n        <translation>搜索字符串</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find Next</source>\n        <translation>查找下一条</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find</source>\n        <translation>查找</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"764\"/>\n        <source>Regex</source>\n        <translation>正则表达式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"750\"/>\n        <source>Cannot find more results</source>\n        <translation>找不到更多的结果</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"400\"/>\n        <source>Try to decompress:</source>\n        <translation>尝试解压:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"401\"/>\n        <source>Decompressed:</source>\n        <translation>解压:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"491\"/>\n        <source>Cannot decompress value using </source>\n        <translation>无法解压以</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"751\"/>\n        <source>Cannot find any results</source>\n        <translation>找不到任何结果</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"899\"/>\n        <source>Binary value is too large to display</source>\n        <translation>二进制内容太长无法展示</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"366\"/>\n        <source>View as:</source>\n        <translation>查看</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"502\"/>\n        <source>Large value (&gt;150kB). Formatters are not available.</source>\n        <translation>键值内容过大（&gt;150kB），格式化配置无效。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"18\"/>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"30\"/>\n        <source>Score</source>\n        <translation>分数</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"194\"/>\n        <source>Path to dump.rdb file</source>\n        <translation>导出为 dump.rdb 文件的路径</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"196\"/>\n        <source>Select dump.rdb</source>\n        <translation>选择 dump.rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"18\"/>\n        <source>ID</source>\n        <translation>ID</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"61\"/>\n        <source>Value (represented as JSON object)</source>\n        <translation>值 (表示为 JSON 对象)</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"127\"/>\n        <source>The row has been changed on server.Reload and try again.</source>\n        <translation>服务端该行内容已经改变，请重新加载。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/bulkoperationsmanager.cpp\" line=\"131\"/>\n        <source>Failed to perform actions on %1 keys. </source>\n        <translation>对键 %1 执行操作时失败。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"12\"/>\n        <source>Cannot copy key </source>\n        <translation>无法复制键 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"123\"/>\n        <source>Source connection error</source>\n        <translation>源连接错误</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"135\"/>\n        <source>Target connection error</source>\n        <translation>目标连接错误</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/deleteoperation.cpp\" line=\"11\"/>\n        <source>Cannot remove key </source>\n        <translation>无法清除键 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"17\"/>\n        <source>Cannot execute command </source>\n        <translation>无法执行命令 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"26\"/>\n        <source>Invalid regexp for keys filter.</source>\n        <translation>键筛选表达式无效</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"39\"/>\n        <source>Cannot get the list of affected keys</source>\n        <translation>无法获取受到影响的键列表</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/ttloperation.cpp\" line=\"11\"/>\n        <source>Cannot set TTL for key </source>\n        <translation>无法给该键设置TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/abstractnamespaceitem.cpp\" line=\"381\"/>\n        <source>Your redis-server doesn&apos;t support &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; commands.</source>\n        <translation>你的Redis服务端不支持 &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; 指令</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"138\"/>\n        <source>Key was added. Do you want to reload keys in selected namespace?</source>\n        <translation>键已经添加。需要重新加载选中的命名空间中的键吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"147\"/>\n        <source>Network is not accessible. Please ensure that you have internet access and try again.</source>\n        <translation>网络无效，请确保已经连上互联网，然后重试。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"190\"/>\n        <source>Invalid login or password</source>\n        <translation>无效的账号或密码</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"200\"/>\n        <source>Too many requests from your IP</source>\n        <translation>过多请求来源你的IP</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"206\"/>\n        <source>Unknown error. Status code %1</source>\n        <translation>未知错误，状态码 %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"321\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"734\"/>\n        <source>Cannot parse server reply</source>\n        <translation>无法解析服务器响应</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"341\"/>\n        <source>Cannot validate token</source>\n        <translation>无法效验令牌</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"347\"/>\n        <source>Cannot login - %1. &lt;br/&gt; Please try again or contact  &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</source>\n        <translation>无法登录 - %1。&lt;br/&gt; 请重试。或直接联系 &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"718\"/>\n        <source>Expired activation code</source>\n        <translation>激活码已过期</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"731\"/>\n        <source>Invalid activation code</source>\n        <translation>无效的激活码</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"588\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"614\"/>\n        <source>Cannot save the update. Disk is full or download folder is not writable.</source>\n        <translation>无法保存更新文件，磁盘已满或下载目录无法写入。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"666\"/>\n        <source>Download was canceled</source>\n        <translation>下载已取消</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"673\"/>\n        <source>Network error</source>\n        <translation>网络错误</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/FilePathInput.qml\" line=\"27\"/>\n        <source>Select File</source>\n        <translation>选择文件</translation>\n    </message>\n    <message>\n        <source>Save to File</source>\n        <translation type=\"obsolete\">保存到文件</translation>\n    </message>\n    <message>\n        <source>Save Value</source>\n        <translation type=\"obsolete\">保存内容</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"913\"/>\n        <source>Save value to file</source>\n        <translation>保存内容到文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Raw Value to File</source>\n        <translation>保存原始内容到文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Formatted Value to File</source>\n        <translation>保存格式化后的内容到文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Raw Value</source>\n        <translation>保存原始内容</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Formatted Value</source>\n        <translation>保存格式化后的内容</translation>\n    </message>\n    <message>\n        <source>Save raw value to file</source>\n        <translation type=\"vanished\">保存原始内容到文件</translation>\n    </message>\n    <message>\n        <source>Save formatted value to file</source>\n        <translation type=\"vanished\">保存格式化后的内容到文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"51\"/>\n        <source>Value was saved to file:</source>\n        <translation>内容已保存到文件：</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/abstractoperation.cpp\" line=\"38\"/>\n        <source>Cannot connect to redis-server</source>\n        <translation>无法连接到Redis服务器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"13\"/>\n        <source>Edit Connection Group</source>\n        <translation>编辑连接组</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"17\"/>\n        <source>Delete Connection Group</source>\n        <translation>删除连接组</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/servergroup.cpp\" line=\"58\"/>\n        <source>Do you really want to delete group &lt;b&gt;with all connections&lt;/b&gt;?</source>\n        <translation>真的要删除组内&lt;b&gt;所有连接&lt;/b&gt;吗？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"8\"/>\n        <source>Order of elements:</source>\n        <translation>要素排序</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"20\"/>\n        <source>Default</source>\n        <translation>默认</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"21\"/>\n        <source>Reverse</source>\n        <translation>反向</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"28\"/>\n        <source>Start date should be less than End date</source>\n        <translation>开始时间应该早于结束时间</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"136\"/>\n        <source>Apply filter</source>\n        <translation>应用筛选条件</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"19\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"25\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"49\"/>\n        <source>Trial is active till</source>\n        <translation>试用到</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"58\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"59\"/>\n        <source>Licensed to</source>\n        <translation>授权给</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"66\"/>\n        <source>Subscription is active until:</source>\n        <translation>订阅到期时间为：</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Manage Subscription</source>\n        <translation>管理订阅</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"30\"/>\n        <source>&lt;span style=&quot;font-size: 11px;&quot;&gt;Powered by awesome &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;open-source software&lt;/a&gt; and &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</source>\n        <translation type=\"unfinished\">&lt;span style=&quot;font-size: 11px;&quot;&gt;Powered by awesome &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;open-source software&lt;/a&gt; and &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"11\"/>\n        <source>Getting Started</source>\n        <translation>使用入门</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"42\"/>\n        <source>Thank you for choosing RESP.app. Let&apos;s make your Redis experience better.</source>\n        <translation>感谢您选择 RESP.app。 让我们给您的 Redis 体验更好。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"60\"/>\n        <source>Connect to Redis-Server</source>\n        <translation>连接到Redis服务器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"73\"/>\n        <source>Read the Docs</source>\n        <translation>阅读文档</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/loadmoreitem.cpp\" line=\"12\"/>\n        <source>Load more keys</source>\n        <translation>加载更多的键</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"24\"/>\n        <source>Yes</source>\n        <translation>是</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"32\"/>\n        <source>No</source>\n        <translation>否</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"19\"/>\n        <source>SSH Passphrase</source>\n        <translation>SSH 密钥</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"21\"/>\n        <source>Unknown</source>\n        <translation>未知</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <source>Passphrase</source>\n        <translation>密钥</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"71\"/>\n        <source>Continue</source>\n        <translation>继续</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/ColorInput.qml\" line=\"43\"/>\n        <source>Select</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/UnsupportedDataType.qml\" line=\"24\"/>\n        <source>Unsupported Redis Data type </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"163\"/>\n        <source>Cannot delete key:\n\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "src/resources/translations/rdm_zh_TW.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_TW\">\n<context>\n    <name>QObject</name>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"360\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"397\"/>\n        <source>Cannot connect to cluster node %1:%2</source>\n        <translation>無法連線到叢集節點 %1:%2</translation>\n    </message>\n    <message>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"408\"/>\n        <location filename=\"../../../3rdparty/qredisclient/src/qredisclient/connection.cpp\" line=\"435\"/>\n        <source>Cannot flush db (%1): %2</source>\n        <translation>無法清空資料庫 (%1): %2</translation>\n    </message>\n</context>\n<context>\n    <name>RESP</name>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"82\"/>\n        <source>Settings directory is not writable</source>\n        <translation>設定儲存資料夾沒有寫入權限</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/app.cpp\" line=\"84\"/>\n        <source>RESP.app can&apos;t save connections file to settings directory. Please change file permissions or restart RESP.app as administrator.</source>\n        <translation>RESP.app 無法儲存設定檔。請更改檔寫入權限或者以管理員模式啟動 RESP.app。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"81\"/>\n        <source>Cannot rename key %1: %2</source>\n        <translation>無法重新命名鍵 %1: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"125\"/>\n        <source>Cannot persist key &apos;%1&apos;. &lt;br&gt; Key does not exist or does not have an assigned TTL value</source>\n        <translation>無法將鍵持久化 &apos;%1&apos; &lt;br&gt; 鍵不存在或是不會逾時</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"160\"/>\n        <source>Cannot parse scan response</source>\n        <translation>無法解析 scan 的結果</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"328\"/>\n        <source>Server returned unexpected response: </source>\n        <translation>伺服器返回未預期的回應: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"106\"/>\n        <source>Cannot set TTL for key %1</source>\n        <translation>無法設定鍵 %1 的 TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"274\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"285\"/>\n        <source>Cannot load rows for key %1: %2</source>\n        <translation>無法載入鍵 %1 的資料: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"42\"/>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"75\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"14\"/>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"41\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"12\"/>\n        <location filename=\"../../app/models/key-models/setkey.cpp\" line=\"33\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"44\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"77\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"48\"/>\n        <location filename=\"../../app/models/key-models/stream.cpp\" line=\"59\"/>\n        <source>Invalid row</source>\n        <translation>無效資料</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"113\"/>\n        <source>Value with the same key already exists</source>\n        <translation>已經存在同名的鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"184\"/>\n        <location filename=\"../../app/models/key-models/abstractkey.h\" line=\"340\"/>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"151\"/>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"84\"/>\n        <source>Connection error: </source>\n        <translation>連線錯誤: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/hashkey.cpp\" line=\"136\"/>\n        <location filename=\"../../app/models/key-models/sortedsetkey.cpp\" line=\"136\"/>\n        <source>Data was loaded from server partially.</source>\n        <translation>部分資料已經從伺服器載入。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"26\"/>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"38\"/>\n        <source>Cannot load key %1, connection error occurred: %2</source>\n        <translation>無法載入鍵 %1，連線發生錯誤: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"49\"/>\n        <source>Cannot load key %1 because it doesn&apos;t exist in database. Please reload connection tree and try again.</source>\n        <translation>無法載入鍵 %1，資料庫中不存在該鍵，請重新載入連線樹後重試。</translation>\n    </message>\n    <message>\n        <source>Cannot load TTL for key %1, connection error occurred: %2</source>\n        <translation type=\"vanished\">無法載入鍵 %1 的 TTL 值，連線發生錯誤: %2</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"78\"/>\n        <source>Cannot retrieve type of the key: </source>\n        <translation>無法取得鍵的類型: </translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/keyfactory.cpp\" line=\"122\"/>\n        <source>Cannot open file with key value</source>\n        <translation>無法以鍵值開啟檔案</translation>\n    </message>\n    <message>\n        <source>Unsupported Redis Data type %1</source>\n        <translation type=\"vanished\">不支援的 Redis 資料類型 %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"129\"/>\n        <source>Cannot connect to server &apos;%1&apos;. Check log for details.</source>\n        <translation>無法連線到伺服器 &apos;%1&apos; 。細節請查看紀錄檔。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"139\"/>\n        <source>Open Source version of RESP.app &lt;b&gt;doesn&apos;t support SSH tunneling&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt; To get fully-featured application, please buy subscription on &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt;. &lt;br/&gt;&lt;br /&gt;Every single subscription gives us funds to continue the development process and provide support to our users. &lt;br /&gt;If you have any questions please feel free to contact us at &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; or join &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram chat&lt;/a&gt;.</source>\n        <translation>開源版本的 RESP.app &lt;b&gt;不支援 SSH 隧道功能&lt;/b&gt;。&lt;br /&gt;&lt;br /&gt;若要取得完整功能的程式，請在 &lt;a href=&apos;https://resp.app/subscriptions&apos;&gt;resp.app&lt;/a&gt; 上購買訂閱。&lt;br/&gt;&lt;br /&gt;每個訂閱都是我們繼續開發以及支援使用者的原動力。&lt;br /&gt;如果你有任何問題，請聯絡 &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt; 或是加入 &lt;a href=&apos;https://t.me/RedisDesktopManager&apos;&gt;Telegram 聊天群組&lt;/a&gt;。</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"229\"/>\n        <source>Cannot load keys: %1</source>\n        <translation>無法載入鍵: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"336\"/>\n        <source>Delete key error: %1</source>\n        <translation>刪除鍵時發生錯誤:</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"477\"/>\n        <source>Cannot determine amount of used memory by key: %1</source>\n        <translation>無法判定鍵所消耗的記憶體: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/treeoperations.cpp\" line=\"416\"/>\n        <source>Cannot flush database: </source>\n        <translation>無法清空資料庫: </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/common/tabmodel.cpp\" line=\"43\"/>\n        <source>Invalid Connection. Check connection settings.</source>\n        <translation>無效連線，請檢查連線設定。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"251\"/>\n        <source>Live update was disabled due to exceeded keys limit. Please specify filter more carefully or change limit in settings.</source>\n        <translation>由於超出載入鍵的數量限制，同步更新功能已經關閉。請設定更精確的篩選條件或更改載入限制設定。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"304\"/>\n        <source>Key was added. Do you want to reload keys in selected database?</source>\n        <translation>已經添加鍵。需要重新載入該資料庫的鍵名嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"312\"/>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"143\"/>\n        <source>Key was added</source>\n        <translation>已經插入鍵</translation>\n    </message>\n    <message>\n        <source>Another operation is currently in progress</source>\n        <translation type=\"vanished\">另一項操作正在進行中</translation>\n    </message>\n    <message>\n        <source>Please wait until another operation will be finished.</source>\n        <translation type=\"vanished\">請耐心等待另一項操作完成。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"327\"/>\n        <source>Do you really want to remove all keys from this database?</source>\n        <translation>確定要刪除該資料庫裡面所有的鍵嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"73\"/>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"75\"/>\n        <source>Cannot load databases:\n\n</source>\n        <translation>無法載入資料庫:\n\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/databaseitem.cpp\" line=\"250\"/>\n        <source>Live update was disabled</source>\n        <translation>同步更新已經禁止</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"190\"/>\n        <source>Rename key</source>\n        <translation>重新命名鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"199\"/>\n        <source>New name:</source>\n        <translation>新名稱:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"21\"/>\n        <source>Total pages: </source>\n        <translation>總頁數: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"45\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"222\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"358\"/>\n        <source>Size: </source>\n        <translation>大小: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"228\"/>\n        <source>TTL:</source>\n        <translation>TTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"233\"/>\n        <source>Set key TTL</source>\n        <translation>設定鍵的 TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"242\"/>\n        <source>New TTL:</source>\n        <translation>新的 TTL:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"298\"/>\n        <source>Delete</source>\n        <translation>刪除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"23\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"303\"/>\n        <source>Delete key</source>\n        <translation>刪除鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"38\"/>\n        <source>Changes are not saved</source>\n        <translation>並未儲存變更</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"39\"/>\n        <source>Do you want to close key tab without saving changes?</source>\n        <translation>要不儲存變更就關閉頁籤嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"260\"/>\n        <source>Persist key</source>\n        <translation>將鍵持久化</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"304\"/>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"153\"/>\n        <source>Do you really want to delete this key?</source>\n        <translation>確定要刪除該鍵？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"140\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"318\"/>\n        <source>Reload Value</source>\n        <translation>重新載入鍵值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"22\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"31\"/>\n        <source>Add Row</source>\n        <translation>插入列</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"30\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"526\"/>\n        <source>Add Element to HLL</source>\n        <translation>新增元素到 HHL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"68\"/>\n        <source>Add</source>\n        <translation>新增</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"101\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"122\"/>\n        <source>Delete row</source>\n        <translation>刪除列</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"107\"/>\n        <source>The row is the last one in the key. After removing it key will be deleted.</source>\n        <translation>此列資料是該鍵最後一列。刪除此列資料，該鍵將會被刪除。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"109\"/>\n        <source>Do you really want to remove this row?</source>\n        <translation>確定要刪除此列資料嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"164\"/>\n        <source>Search on page...</source>\n        <translation>頁面搜尋...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"191\"/>\n        <source>Full Search</source>\n        <translation>全文搜尋</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"191\"/>\n        <source>Value and Console tabs related to this connection will be closed. Do you want to continue?</source>\n        <translation>所有與該連線相關的鍵值對話方塊和指令操作對話方塊都將被關閉，確定要繼續嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/serveritem.cpp\" line=\"204\"/>\n        <source>Do you really want to delete connection?</source>\n        <translation>確定要刪除連線？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"13\"/>\n        <source>Connected to cluster.\n</source>\n        <translation>已連線到叢集伺服器。\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"16\"/>\n        <source>Connected.\n</source>\n        <translation>已連線。\n</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"60\"/>\n        <source>Switch to %1 mode. Close console tab to stop listen for messages.</source>\n        <translation>切換為 %1 模式。關閉頁籤以停止監聽訊息。</translation>\n    </message>\n    <message>\n        <source>Switch to Pub/Sub mode. Close console tab to stop listen for messages.</source>\n        <translation type=\"vanished\">切斷到 發布/訂閱 模式。關閉控制台以停止監聽訊息。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/console/consolemodel.cpp\" line=\"69\"/>\n        <source>Subscribe error: %1</source>\n        <translation>訂閱錯誤: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/server-actions/serverstatsmodel.cpp\" line=\"36\"/>\n        <source>Server %0</source>\n        <translation>伺服器 %0</translation>\n    </message>\n    <message>\n        <source>Can&apos;t find formatter with name: %1</source>\n        <translation type=\"vanished\">找不到格式化工具: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"109\"/>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"147\"/>\n        <source>Can&apos;t find formatter: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"115\"/>\n        <source>Invalid callback</source>\n        <translation>無效回調</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"204\"/>\n        <source>Can&apos;t load list of available formatters from extension server: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/extension-server/dataformattermanager.cpp\" line=\"260\"/>\n        <source>Can&apos;t encode value: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Cannot decode value using %1 formatter. </source>\n        <translation type=\"vanished\">無法使用格式化工具解碼值 %1</translation>\n    </message>\n    <message>\n        <source>Cannot validate value using %1 formatter.</source>\n        <translation type=\"vanished\">無法使用格式化工具驗證值 %1</translation>\n    </message>\n    <message>\n        <source>Cannot encode value using %1 formatter. </source>\n        <translation type=\"vanished\">無法使用格式化工具編碼值 %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"26\"/>\n        <source>Loading key: %1 from db %2</source>\n        <translation>從資料庫 %2 中載入鍵 %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"68\"/>\n        <source>Cannot open value tab</source>\n        <translation>無法打開鍵值對話方塊</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"97\"/>\n        <source>Connection error</source>\n        <translation>連接錯誤</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/tabsmodel.cpp\" line=\"115\"/>\n        <source>Connection error. Can&apos;t open value tab. </source>\n        <translation>連線錯誤，無法打開鍵值對話方塊。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"176\"/>\n        <source>Cannot reload key value: %1</source>\n        <translation>無法重新載入鍵值: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/value-editor/valueviewmodel.cpp\" line=\"228\"/>\n        <source>Cannot load key value: %1</source>\n        <translation>無法載入鍵值: %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"29\"/>\n        <source>Connect to Redis Server</source>\n        <translation>連線到 Redis 伺服器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"117\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"205\"/>\n        <source>Import</source>\n        <translation>匯入</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"50\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"69\"/>\n        <source>Import Connections</source>\n        <translation>匯入連線</translation>\n    </message>\n    <message>\n        <source>Export</source>\n        <translation type=\"vanished\">匯出</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"58\"/>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"74\"/>\n        <source>Export Connections</source>\n        <translation>匯出連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"100\"/>\n        <source>Report issue</source>\n        <translation>回報問題</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"107\"/>\n        <source>Documentation</source>\n        <translation>說明文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"114\"/>\n        <source>Join Telegram Chat</source>\n        <translation>加入 Telegram 聊天群組</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"121\"/>\n        <source>Follow</source>\n        <translation>追隨</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"128\"/>\n        <source>Star on GitHub!</source>\n        <translation>在 GitHub 上給個 Star</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"136\"/>\n        <source>Log</source>\n        <translation>紀錄</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"144\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"13\"/>\n        <source>Extension Server</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/AppToolBar.qml\" line=\"154\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"13\"/>\n        <source>Settings</source>\n        <translation>設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>New Connection Settings</source>\n        <translation>新連線設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"144\"/>\n        <source>How to connect</source>\n        <translation>如何連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"151\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"48\"/>\n        <source>Connection Settings</source>\n        <translation>連線設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"180\"/>\n        <source>Create connection from Redis URL</source>\n        <translation>以 Redis URL 建立連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"233\"/>\n        <source>Learn more about Redis URL:  </source>\n        <translation>了解更多關於 Redis URL: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"240\"/>\n        <source>Connection guides</source>\n        <translation>連線嚮導</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"260\"/>\n        <source>Local or Public Redis</source>\n        <translation>本機或公開的 Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"276\"/>\n        <source>Redis with SSL/TLS</source>\n        <translation>使用 SSL/TLS 的 Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"292\"/>\n        <source>SSH tunnel</source>\n        <translation>SSH 隧道</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"308\"/>\n        <source>UNIX socket</source>\n        <translation>UNIX socket</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"395\"/>\n        <source>Cannot figure out how to connect to your redis-server?</source>\n        <translation>不知道如何連線到您的 Redis 伺服器嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"401\"/>\n        <source>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;Read the Docs&lt;/a&gt;, &lt;a href=&quot;mailto:support@resp.app&quot;&gt;Contact Support&lt;/a&gt; or ask for help in our &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram Group&lt;/a&gt;</source>\n        <translation>&lt;a href=&quot;https://docs.resp.app/en/latest/quick-start/&quot;&gt;閱讀文件&lt;/a&gt;，&lt;a href=&quot;mailto:support@resp.app&quot;&gt;聯絡客服&lt;/a&gt;或是在 &lt;a href=&quot;https://t.me/RedisDesktopManager&quot;&gt;Telegram 群組&lt;/a&gt; 內請求協助。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"413\"/>\n        <source>Don&apos;t have running Redis?</source>\n        <translation>Redis 沒有在執行中嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"419\"/>\n        <source>Spin up hassle-free Redis on Digital Ocean</source>\n        <translation>快速使用 Digital Ocean 上的 Redis</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"431\"/>\n        <source>Skip</source>\n        <translation>略過</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"466\"/>\n        <source>Name:</source>\n        <translation>名稱:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"472\"/>\n        <source>Connection Name</source>\n        <translation>連線名稱</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"478\"/>\n        <source>Address:</source>\n        <translation>位址:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"483\"/>\n        <source>redis-server host</source>\n        <translation>Redis 伺服器位址</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"507\"/>\n        <source>(Optional) redis-server authentication password</source>\n        <translation>(可選) Redis 伺服器的認證密碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"526\"/>\n        <source>Security</source>\n        <translation>安全設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"573\"/>\n        <source>Public Key:</source>\n        <translation>公鑰:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"579\"/>\n        <source>(Optional) Public Key in PEM format</source>\n        <translation>(可選) PEM 格式的公鑰</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"581\"/>\n        <source>Select public key in PEM format</source>\n        <translation>選擇 PEM 格式的公鑰</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"592\"/>\n        <source>(Optional) Private Key in PEM format</source>\n        <translation>(可選) PEM 格式的私鑰</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"594\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"703\"/>\n        <source>Select private key in PEM format</source>\n        <translation>選擇 PEM 格式的私鑰</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"599\"/>\n        <source>Authority:</source>\n        <translation>授權證書:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"605\"/>\n        <source>(Optional) Authority in PEM format</source>\n        <translation>(可選) PEM 格式的授權證書</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"607\"/>\n        <source>Select authority file in PEM format</source>\n        <translation>選擇 PEM 格式的授權證書</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"553\"/>\n        <source>SSH Tunnel</source>\n        <translation>SSH 隧道</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"630\"/>\n        <source>SSH Address:</source>\n        <translation>SSH 位址:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"634\"/>\n        <source>Remote Host with SSH server</source>\n        <translation>SSH 遠端伺服器位址</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"642\"/>\n        <source>SSH User:</source>\n        <translation>SSH 使用者:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"648\"/>\n        <source>Valid SSH User Name</source>\n        <translation>有效的 SSH 使用者名稱</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"586\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"683\"/>\n        <source>Private Key</source>\n        <translation>私鑰</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"701\"/>\n        <source>Path to Private Key in PEM format</source>\n        <translation>PEM 格式私鑰路徑</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"711\"/>\n        <source>&lt;b&gt;Tip:&lt;/b&gt; Use &lt;code&gt;⌘ + Shift + .&lt;/code&gt; to show hidden files and folders in dialog</source>\n        <translation>&lt;b&gt;提示: &lt;/b&gt; &lt;code&gt;⌘ + Shift + .&lt;/code&gt; 可以顯示隱藏的檔案與資料夾</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"87\"/>\n        <source>Password</source>\n        <translation>密碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"737\"/>\n        <source>SSH User Password</source>\n        <translation>SSH 使用者密碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"759\"/>\n        <source>Enable TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</source>\n        <translation>啟用 TLS-over-SSH (&lt;b&gt;AWS ElastiCache&lt;/b&gt; &lt;b&gt;Encryption in-transit&lt;/b&gt;)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"156\"/>\n        <source>Advanced Settings</source>\n        <translation>進階設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"494\"/>\n        <source>For better network performance please use 127.0.0.1</source>\n        <translation>使用 127.0.0.1 以提高網路性能</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"512\"/>\n        <source>Username:</source>\n        <translation>使用者名稱:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"518\"/>\n        <source>(Optional) redis-server authentication username (Redis &gt;6.0)</source>\n        <translation>(可選) Redis 伺服器認證使用者名稱 (Redis &gt;6.0)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"540\"/>\n        <source>SSL / TLS</source>\n        <translation>SSL / TLS</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"612\"/>\n        <source>Enable strict mode:</source>\n        <translation>啟用嚴格模式:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"657\"/>\n        <source>Use SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"667\"/>\n        <source>(Optional) Custom SSH Agent Path</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"669\"/>\n        <source>Select SSH Agent</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"678\"/>\n        <source>Additional configuration is required to enable SSH Agent support</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"734\"/>\n        <source>Passphrase for provided private key</source>\n        <translation>私鑰的密詞</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"736\"/>\n        <source>Password request will be prompt prior to connection</source>\n        <translation>將會在連接前詢問密碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"747\"/>\n        <source>Ask for password</source>\n        <translation>詢問密碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"796\"/>\n        <source>Keys loading</source>\n        <translation>鍵的載入</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"800\"/>\n        <source>Default filter:</source>\n        <translation>預設篩選器:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"806\"/>\n        <source>Pattern which defines loaded keys from redis-server</source>\n        <translation>指定載入鍵名運算式:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"812\"/>\n        <source>Namespace Separator:</source>\n        <translation>命名空間的分隔符號:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"819\"/>\n        <source>Separator used for namespace extraction from keys</source>\n        <translation>從鍵名中提取命名空間用的分隔符號</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"825\"/>\n        <source>Timeouts &amp; Limits</source>\n        <translation>超時 &amp; 限制</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"829\"/>\n        <source>Connection Timeout (sec):</source>\n        <translation>連線逾時 (秒):</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"842\"/>\n        <source>Execution Timeout (sec):</source>\n        <translation>執行超時 (秒):</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"853\"/>\n        <source>Databases discovery limit:</source>\n        <translation>資料庫探索上限:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"867\"/>\n        <source>Cluster</source>\n        <translation>叢集</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"871\"/>\n        <source>Change host on cluster redirects:</source>\n        <translation>在叢集重定向後改變 host :</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"881\"/>\n        <source>Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"885\"/>\n        <source>Default value formatter:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"901\"/>\n        <source>Auto detect (JSON / Plain Text / HEX)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"902\"/>\n        <source>Last selected</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"903\"/>\n        <source>Select formatter ...</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"952\"/>\n        <source>Appearance</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"956\"/>\n        <source>Icon color:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1021\"/>\n        <source>Invalid settings detected!</source>\n        <translation>檢測到無效的設定！</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"992\"/>\n        <source>Test Connection</source>\n        <translation>測試連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/OkDialogOverlay.qml\" line=\"20\"/>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"111\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1029\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"163\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"319\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"903\"/>\n        <source>OK</source>\n        <translation>確定</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"294\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"508\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"402\"/>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"44\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"61\"/>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"89\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1057\"/>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"175\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"331\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"172\"/>\n        <location filename=\"../../qml/value-editor/ValueTableActions.qml\" line=\"89\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"268\"/>\n        <source>Cancel</source>\n        <translation>取消</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"48\"/>\n        <source>General</source>\n        <translation>一般</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"53\"/>\n        <source>Application will be restarted to apply these settings.</source>\n        <translation>程式將會重新啟動以套用此設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"73\"/>\n        <source>Language</source>\n        <translation>語言</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"393\"/>\n        <source>Application will be restarted to apply this setting.</source>\n        <translation>程式將會重新啟動以套用新的設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"85\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"174\"/>\n        <source>Font</source>\n        <translation>字體</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"97\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"186\"/>\n        <source>Font Size</source>\n        <translation>字體大小</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"110\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"124\"/>\n        <source>Dark Mode</source>\n        <translation>深色模式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"200\"/>\n        <source>Maximum Formatted Value Size</source>\n        <translation>最大格式化長度</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"201\"/>\n        <source>Size in bytes</source>\n        <translation>長度（位元組）</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"259\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"392\"/>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"138\"/>\n        <source>Use system proxy settings</source>\n        <translation>使用系統的代理設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"150\"/>\n        <source>Use system proxy only for HTTP(S) requests</source>\n        <translation>只為 HTTP(S) 使用系統的代理</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"156\"/>\n        <source>Value Editor</source>\n        <translation>值編輯器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"213\"/>\n        <source>Maximum amount of items per page</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"218\"/>\n        <source>Connections Tree</source>\n        <translation>連線列表</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"236\"/>\n        <source>Show namespaced keys on top</source>\n        <translation>置頂有命名空間的鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"246\"/>\n        <source>Reopen namespaces on reload</source>\n        <translation>重新載入時重新打開命名空間</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"247\"/>\n        <source>(Disable to improve treeview performance)</source>\n        <translation>(停用樹狀檢視以提高性能)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"258\"/>\n        <source>Show only last part for namespaced keys</source>\n        <translation>對有命名空間的鍵只顯示最後一部分</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"270\"/>\n        <source>Limit for SCAN command</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"282\"/>\n        <source>Maximum amount of rendered child items</source>\n        <translation>子項目的最大渲染數量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"294\"/>\n        <source>Live update maximum allowed keys</source>\n        <translation>同步更新最大允許鍵數量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/settings/GlobalSettings.qml\" line=\"306\"/>\n        <source>Live update interval (in seconds)</source>\n        <translation>同步更新時間 (秒)</translation>\n    </message>\n    <message>\n        <source>External Value View Formatters</source>\n        <translation type=\"vanished\">外部的值格式化工具</translation>\n    </message>\n    <message>\n        <source>Formatters path: %0</source>\n        <translation type=\"vanished\">格式化工具路徑: %0</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"61\"/>\n        <source>Server Url:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"75\"/>\n        <source>Basic Auth:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"81\"/>\n        <source>User</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"99\"/>\n        <source>Response timeout  (in seconds)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"109\"/>\n        <source>Available Data Formatters</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"117\"/>\n        <source>Reload</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"135\"/>\n        <source>Id</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"141\"/>\n        <source>Name</source>\n        <translation>名稱</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/extension-server/ExtensionServerSettings.qml\" line=\"147\"/>\n        <source>Read Only</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"29\"/>\n        <source>Version</source>\n        <translation>版本</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1001\"/>\n        <source>Quick Start Guide</source>\n        <translation>快速入門指南</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"134\"/>\n        <source>Successful connection to redis-server</source>\n        <translation>成功連線到 Redis 伺服器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"137\"/>\n        <source>Can&apos;t connect to redis-server</source>\n        <translation>無法連線到 Redis 伺服器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"321\"/>\n        <source>Add Group</source>\n        <translation>新增分組</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"336\"/>\n        <source>Regroup connections</source>\n        <translation>重組連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/app.qml\" line=\"358\"/>\n        <source>Exit Regroup Mode</source>\n        <translation>離開分組模式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"10\"/>\n        <source>Bulk Operations Manager</source>\n        <translation>批次操作管理器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Invalid RDB path</source>\n        <translation>無效的 RDB 路徑</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"66\"/>\n        <source>Please specify valid path to RDB file</source>\n        <translation>請指定有效的 RDB 檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"88\"/>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"89\"/>\n        <source>Delete keys</source>\n        <translation>刪除鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"98\"/>\n        <source>Set TTL</source>\n        <translation>設定 TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"106\"/>\n        <source>Copy keys to another database</source>\n        <translation>複製鍵到其他資料庫</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"107\"/>\n        <source>Copy keys</source>\n        <translation>複製鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"116\"/>\n        <source>Import data from rdb file</source>\n        <translation>從 RDB 檔案中匯入資料</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"151\"/>\n        <source>Redis Server:</source>\n        <translation>Redis 伺服器:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"163\"/>\n        <source>Database number:</source>\n        <translation>資料庫編號:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"183\"/>\n        <source>Path to RDB file:</source>\n        <translation>RDB 檔案的路徑:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"205\"/>\n        <source>Select DB in RDB file:</source>\n        <translation>選擇 RDB 檔案中的資料庫:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Key pattern:</source>\n        <translation>鍵名運算式:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"225\"/>\n        <source>Import keys that match &lt;b&gt;regex&lt;/b&gt;:</source>\n        <translation>匯入符合&lt;b&gt;正規表達式&lt;/b&gt;的鍵:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"272\"/>\n        <source>Destination Redis Server:</source>\n        <translation>目標 Redis 伺服器:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"283\"/>\n        <source>Destination Redis Server Database Index:</source>\n        <translation>目標資料庫編號:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show matched keys</source>\n        <translation>顯示符合的鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"320\"/>\n        <source>Show Affected keys</source>\n        <translation>顯示受影響的鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Affected keys:</source>\n        <translation>受影響的鍵:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"343\"/>\n        <source>Matched keys:</source>\n        <translation>符合的鍵:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"368\"/>\n        <source>Bulk Operation finished.</source>\n        <translation>批次操作完成。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"373\"/>\n        <source>Bulk Operation finished with errors</source>\n        <translation>批次操作完成但途中曾發生錯誤</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"425\"/>\n        <source>Processed: </source>\n        <translation>已處理: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"427\"/>\n        <source>Getting list of affected keys...</source>\n        <translation>正在取得受影響的鍵的清單...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"475\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1097\"/>\n        <source>Success</source>\n        <translation>成功</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"501\"/>\n        <source>Confirmation</source>\n        <translation>確認</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"502\"/>\n        <source>Do you really want to perform bulk operation?</source>\n        <translation>確認要執行批次操作？</translation>\n    </message>\n    <message>\n        <source>Sign in with resp.app account</source>\n        <translation type=\"vanished\">以 resp.app 的帳號登入</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"231\"/>\n        <source>Renew your subscription</source>\n        <translation>續期您的訂閱</translation>\n    </message>\n    <message>\n        <source>Your trial has ended.</source>\n        <translation type=\"vanished\">您的試用已經到期</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"234\"/>\n        <source>You have no active subscription</source>\n        <translation>您沒有可用的訂閱</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"237\"/>\n        <source>No internet connection</source>\n        <translation>無網絡連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"240\"/>\n        <source>Your trial has ended</source>\n        <translation>您的試用已結束</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"252\"/>\n        <source>To use this version you need to renew your subscription.</source>\n        <translation>您必須續期訂閱已繼續使用此版本。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"257\"/>\n        <source>If you’re behind a proxy please enable </source>\n        <translation>如果您處於代理之中，請啟用 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"261\"/>\n        <source> option before sign-in.</source>\n        <translation> 選項（在登入前）。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"11\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"317\"/>\n        <source>Sign in with RESP.app account</source>\n        <translation>以 resp.app 的帳號登入</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"255\"/>\n        <source>Please make sure that RESP.app is not blocked by a firewall and you have an internet connection.</source>\n        <translation>請確保 RESP.app 沒有被防火牆阻擋，並且網絡連線正常。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"264\"/>\n        <source>Please purchase a subscription to continue using RESP.app.</source>\n        <translation>請購買訂閱以繼續使用 RESP.app 。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"268\"/>\n        <source>If you have any questions please contact support </source>\n        <translation>如果您有任何問題，請聯絡客服 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"279\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Renew Subscription</source>\n        <translation>續期訂閱</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"280\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"32\"/>\n        <source>Buy Subscription</source>\n        <translation>購買訂閱</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"293\"/>\n        <source>Try Again</source>\n        <translation>重試</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"331\"/>\n        <source>Email:</source>\n        <translation>Email:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"347\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"501\"/>\n        <source>Password:</source>\n        <translation>密碼:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"370\"/>\n        <location filename=\"../../qml/common/PasswordInput.qml\" line=\"29\"/>\n        <source>Show password</source>\n        <translation>顯示密碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"376\"/>\n        <source>Forgot password?</source>\n        <translation>忘記密碼？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"404\"/>\n        <source>Sign In</source>\n        <translation>登入</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"409\"/>\n        <source>Please enter email &amp; password to sign in.</source>\n        <translation>請輸入 email 與密碼登入。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"422\"/>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"444\"/>\n        <source>Offline Activation</source>\n        <translation>離線啟用</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"465\"/>\n        <source>Paste Activation code here</source>\n        <translation>在此處貼上啟用碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"481\"/>\n        <source>Where can I find my activation code?</source>\n        <translation>我能在哪裡找到我的啟用碼？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"490\"/>\n        <source>Activate</source>\n        <translation>啟用</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SignInDialog.qml\" line=\"495\"/>\n        <source>Please enter valid activation code.</source>\n        <translation>請輸入啟用碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/TreeItemDelegate.qml\" line=\"220\"/>\n        <source> (Removed)</source>\n        <translation> (已移除)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"77\"/>\n        <source>Open Keys Filter</source>\n        <translation>打開鍵篩選器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"81\"/>\n        <source>Reload Keys in Database</source>\n        <translation>重新載入資料庫中的鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"85\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"30\"/>\n        <source>Add New Key</source>\n        <translation>新增鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Disable Live Update</source>\n        <translation>停用同步更新</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"91\"/>\n        <source>Enable Live Update</source>\n        <translation>啟用同步更新</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"95\"/>\n        <source>Open Console</source>\n        <translation>打開控制台</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"98\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"32\"/>\n        <source>Analyze Used Memory</source>\n        <translation>分析記憶體用量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"100\"/>\n        <source>Bulk Operations</source>\n        <translation>批次操作</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"121\"/>\n        <source>Flush Database</source>\n        <translation>清空資料庫</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"124\"/>\n        <source>Delete keys with filter</source>\n        <translation>使用篩選器來刪除鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"97\"/>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"127\"/>\n        <source>Set TTL for multiple keys</source>\n        <translation>為多個鍵設定 TTL</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"130\"/>\n        <source>Copy keys from this database to another</source>\n        <translation>從此資料庫中複製鍵到另一個資料庫</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"133\"/>\n        <source>Import keys from RDB file</source>\n        <translation>從 RDB 檔案中匯入鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"136\"/>\n        <source>Back</source>\n        <translation>返回</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/key.qml\" line=\"22\"/>\n        <source>Copy Key Name</source>\n        <translation>複製鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"29\"/>\n        <source>Reload Namespace</source>\n        <translation>重新載入命名空間</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"31\"/>\n        <source>Copy Namespace Pattern</source>\n        <translation>複製命名空間運算式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"33\"/>\n        <source>Delete Namespace</source>\n        <translation>刪除命名空間</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/database.qml\" line=\"71\"/>\n        <location filename=\"../../qml/connections-tree/menu/namespace.qml\" line=\"24\"/>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"14\"/>\n        <source>Disconnect</source>\n        <translation>中斷連線</translation>\n    </message>\n    <message>\n        <source>Server Info</source>\n        <translation type=\"vanished\">伺服器資訊</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"20\"/>\n        <source>Reload Server</source>\n        <translation>重新載入伺服器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"24\"/>\n        <source>Unload All Data</source>\n        <translation>卸載所有資料</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"28\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"10\"/>\n        <source>Edit Connection Settings</source>\n        <translation>編輯連線設定</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"32\"/>\n        <source>Duplicate Connection</source>\n        <translation>複製連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server.qml\" line=\"36\"/>\n        <source>Delete Connection</source>\n        <translation>刪除連線</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"21\"/>\n        <source>Connecting...</source>\n        <translation>連線中...</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"193\"/>\n        <source>Clear</source>\n        <translation>清除</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"234\"/>\n        <source>Arguments</source>\n        <translation>參數</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"240\"/>\n        <source>Description</source>\n        <translation>描述</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"246\"/>\n        <source>Available since</source>\n        <translation>可用自</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/console/RedisConsole.qml\" line=\"297\"/>\n        <source>Close</source>\n        <translation>關閉</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"108\"/>\n        <source>View Server Info</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"130\"/>\n        <source>Redis Version</source>\n        <translation>Redis 版本</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"169\"/>\n        <source>Used memory</source>\n        <translation>已使用記憶體</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"182\"/>\n        <source>Cmd Processed</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"203\"/>\n        <source>Monitor Commands</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"242\"/>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"319\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"105\"/>\n        <source>Clients</source>\n        <translation>連線數</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"377\"/>\n        <source>Server Actions</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Commands Processed</source>\n        <translation type=\"vanished\">已執行指令</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"143\"/>\n        <source>Uptime</source>\n        <translation>上線時間</translation>\n    </message>\n    <message>\n        <source>Total Keys</source>\n        <translation type=\"vanished\">鍵總數</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"156\"/>\n        <source>Hit Ratio</source>\n        <translation>命中率</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"262\"/>\n        <source>Server Stats</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"281\"/>\n        <source>Console</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"321\"/>\n        <source> day(s)</source>\n        <translation> 天</translation>\n    </message>\n    <message>\n        <source>Info</source>\n        <translation type=\"vanished\">資訊</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"53\"/>\n        <source>Commands Per Second</source>\n        <translation>每秒指令數</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"67\"/>\n        <source>Ops/s</source>\n        <translation>操作/秒</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"91\"/>\n        <source>Connected Clients</source>\n        <translation>已連線的客戶端</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"129\"/>\n        <source>Memory Usage</source>\n        <translation>記憶體佔用</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"141\"/>\n        <source>Mb</source>\n        <translation>Mb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"170\"/>\n        <source>Network Input</source>\n        <translation>網路輸入</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"182\"/>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"218\"/>\n        <source>Kb/s</source>\n        <translation>Kb/s</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"206\"/>\n        <source>Network Output</source>\n        <translation>網路輸出</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"242\"/>\n        <source>Total Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerCharts.qml\" line=\"256\"/>\n        <source>Error Replies</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Keys</source>\n        <translation type=\"vanished\">鍵數量</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"36\"/>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"28\"/>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"39\"/>\n        <source>Auto Refresh</source>\n        <translation>自動重整</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"66\"/>\n        <source>Property</source>\n        <translation>屬性</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerConfig.qml\" line=\"72\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"22\"/>\n        <source>Value</source>\n        <translation>值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"68\"/>\n        <source>Subscribe in Console</source>\n        <translation>在控制台中訂閱</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"221\"/>\n        <source>Slowlog</source>\n        <translation>慢紀錄</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerActionTabs.qml\" line=\"299\"/>\n        <source>Pub/Sub Channels</source>\n        <translation>發布/訂閱 頻道</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"38\"/>\n        <source>Enable</source>\n        <translation>啟用</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerPubSub.qml\" line=\"57\"/>\n        <source>Channel Name</source>\n        <translation>頻道名稱</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"54\"/>\n        <source>Command</source>\n        <translation>指令</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"71\"/>\n        <source>Processed at</source>\n        <translation>已處理於</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerSlowlog.qml\" line=\"86\"/>\n        <source>Execution Time (μs)</source>\n        <translation>執行時間 (微秒)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"51\"/>\n        <source>Client Address</source>\n        <translation>客戶端位址</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"57\"/>\n        <source>Age (sec)</source>\n        <translation>連線時長 (秒)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"63\"/>\n        <source>Idle</source>\n        <translation>閒置</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"69\"/>\n        <source>Flags</source>\n        <translation>旗標</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/server-actions/ServerClients.qml\" line=\"75\"/>\n        <source>Current Database</source>\n        <translation>當前資料庫</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"10\"/>\n        <source>Add New Key to </source>\n        <translation>新增鍵到</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"56\"/>\n        <location filename=\"../../qml/value-editor/editors/HashItemEditor.qml\" line=\"17\"/>\n        <source>Key:</source>\n        <translation>鍵:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"66\"/>\n        <source>Type:</source>\n        <translation>類型:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"107\"/>\n        <source>Or Import Value from the file</source>\n        <translation>或從檔案匯入值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"113\"/>\n        <source>(Optional) Any file</source>\n        <translation>(可選) 任何檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"115\"/>\n        <source>Select file with value</source>\n        <translation>以值選擇檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterDialog.qml\" line=\"39\"/>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"127\"/>\n        <location filename=\"../../qml/value-editor/ValueTabs.qml\" line=\"254\"/>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"616\"/>\n        <source>Save</source>\n        <translation>儲存</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Edit Connections Group</source>\n        <translation>編輯連線的群組</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"10\"/>\n        <source>Add New Connections Group</source>\n        <translation>新增連線群組</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/ConnectionGroupDialog.qml\" line=\"29\"/>\n        <source>Group Name:</source>\n        <translation>群組名稱:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"1091\"/>\n        <location filename=\"../../qml/value-editor/AddKeyDialog.qml\" line=\"183\"/>\n        <location filename=\"../../qml/value-editor/editors/formatters/ValueFormatters.qml\" line=\"251\"/>\n        <source>Error</source>\n        <translation>錯誤</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/Pagination.qml\" line=\"12\"/>\n        <source>Page</source>\n        <translation>頁</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"68\"/>\n        <source>Enter valid value</source>\n        <translation>請輸入有效的值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"286\"/>\n        <source>Formatting error</source>\n        <translation>格式化錯誤</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"291\"/>\n        <source>Unknown formatter error (Empty response)</source>\n        <translation>未知格式化錯誤 (沒有回應)</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"363\"/>\n        <source>[Binary]</source>\n        <translation>[二進位制內容]</translation>\n    </message>\n    <message>\n        <source> [Compressed: </source>\n        <translation type=\"vanished\">[被壓縮的: </translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"544\"/>\n        <source>Copy to Clipboard</source>\n        <translation>複製到剪貼簿</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"600\"/>\n        <source>Exit </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"601\"/>\n        <source>Full Screen Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"617\"/>\n        <source>Save Changes</source>\n        <translation>儲存變更</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"719\"/>\n        <source>Search string</source>\n        <translation>搜尋字串</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find Next</source>\n        <translation>尋找下一筆</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"733\"/>\n        <source>Find</source>\n        <translation>尋找</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"764\"/>\n        <source>Regex</source>\n        <translation>正規表示式</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"750\"/>\n        <source>Cannot find more results</source>\n        <translation>找不到更多結果</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"400\"/>\n        <source>Try to decompress:</source>\n        <translation>嘗試解壓縮:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"401\"/>\n        <source>Decompressed:</source>\n        <translation>解壓縮:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"491\"/>\n        <source>Cannot decompress value using </source>\n        <translation>無法解壓縮以</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"751\"/>\n        <source>Cannot find any results</source>\n        <translation>找不到任何結果</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"899\"/>\n        <source>Binary value is too large to display</source>\n        <translation>二進位制內容過長而無法顯示</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"366\"/>\n        <source>View as:</source>\n        <translation>以...開啟:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"502\"/>\n        <source>Large value (&gt;150kB). Formatters are not available.</source>\n        <translation>內容過大 (&gt;150kB) 無法格式化。</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"18\"/>\n        <location filename=\"../../qml/value-editor/editors/SortedSetItemEditor.qml\" line=\"30\"/>\n        <source>Score</source>\n        <translation>分數</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"194\"/>\n        <source>Path to dump.rdb file</source>\n        <translation>dump.rdb 的路徑</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/bulk-operations/BulkOperationsDialog.qml\" line=\"196\"/>\n        <source>Select dump.rdb</source>\n        <translation>選擇 dump.rdb</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"18\"/>\n        <source>ID</source>\n        <translation>ID</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/StreamItemEditor.qml\" line=\"61\"/>\n        <source>Value (represented as JSON object)</source>\n        <translation>值 (以 JSON 物件表示)</translation>\n    </message>\n    <message>\n        <location filename=\"../../app/models/key-models/listkey.cpp\" line=\"127\"/>\n        <source>The row has been changed on server.Reload and try again.</source>\n        <translation>此列資料已在伺服器上被修改，請重新載入後再試一次。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/bulkoperationsmanager.cpp\" line=\"131\"/>\n        <source>Failed to perform actions on %1 keys. </source>\n        <translation>無法在鍵 %1 上執行動作。 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"12\"/>\n        <source>Cannot copy key </source>\n        <translation>無法複製鍵 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"123\"/>\n        <source>Source connection error</source>\n        <translation>來源連線錯誤</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/copyoperation.cpp\" line=\"135\"/>\n        <source>Target connection error</source>\n        <translation>目標連線錯誤</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/deleteoperation.cpp\" line=\"11\"/>\n        <source>Cannot remove key </source>\n        <translation>無法刪除鍵 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"17\"/>\n        <source>Cannot execute command </source>\n        <translation>無法執行指令 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"26\"/>\n        <source>Invalid regexp for keys filter.</source>\n        <translation>鍵篩選器的正規表達式無效</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/rdbimport.cpp\" line=\"39\"/>\n        <source>Cannot get the list of affected keys</source>\n        <translation>無法取得受影響的鍵的清單</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/ttloperation.cpp\" line=\"11\"/>\n        <source>Cannot set TTL for key </source>\n        <translation>無法設定 TTL 給鍵 </translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/abstractnamespaceitem.cpp\" line=\"381\"/>\n        <source>Your redis-server doesn&apos;t support &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; commands.</source>\n        <translation>你的 Redis 伺服器不支援 &lt;a href=&apos;https://redis.io/commands/memory-usage&apos;&gt;&lt;b&gt;MEMORY&lt;/b&gt;&lt;/a&gt; 指令。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/namespaceitem.cpp\" line=\"138\"/>\n        <source>Key was added. Do you want to reload keys in selected namespace?</source>\n        <translation>已新增鍵。你想要重新載入命名空間中的鍵嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"147\"/>\n        <source>Network is not accessible. Please ensure that you have internet access and try again.</source>\n        <translation>無法存取網路。請確認您可以存取網路後重新再試。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"190\"/>\n        <source>Invalid login or password</source>\n        <translation>無效的登入資料</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"200\"/>\n        <source>Too many requests from your IP</source>\n        <translation>您的 IP 發起過多的請求</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"206\"/>\n        <source>Unknown error. Status code %1</source>\n        <translation>未知錯誤。狀態碼 %1</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"321\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"734\"/>\n        <source>Cannot parse server reply</source>\n        <translation>無法解析伺服器回應</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"341\"/>\n        <source>Cannot validate token</source>\n        <translation>無法驗證權杖</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"347\"/>\n        <source>Cannot login - %1. &lt;br/&gt; Please try again or contact  &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</source>\n        <translation>無法登入 - %1 。&lt;br/&gt; 請再試一次或聯絡 &lt;a href=&apos;mailto:support@resp.app&apos;&gt;support@resp.app&lt;/a&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"718\"/>\n        <source>Expired activation code</source>\n        <translation>過期的啟動碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"731\"/>\n        <source>Invalid activation code</source>\n        <translation>無效的啟動碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"588\"/>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"614\"/>\n        <source>Cannot save the update. Disk is full or download folder is not writable.</source>\n        <translation>無法保存更新檔，可能是硬碟已滿或是資料夾無法寫入。</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"666\"/>\n        <source>Download was canceled</source>\n        <translation>下載已被取消</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/api_client.cpp\" line=\"673\"/>\n        <source>Network error</source>\n        <translation>網路錯誤</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/FilePathInput.qml\" line=\"27\"/>\n        <source>Select File</source>\n        <translation>選擇檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/MultilineEditor.qml\" line=\"913\"/>\n        <source>Save value to file</source>\n        <translation>儲存值到檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Raw Value to File</source>\n        <translation>儲存原始值到檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"10\"/>\n        <source>Save Formatted Value to File</source>\n        <translation>儲存格式化的值到檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Raw Value</source>\n        <translation>儲存原始值</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"27\"/>\n        <source>Save Formatted Value</source>\n        <translation>儲存格式化的值</translation>\n    </message>\n    <message>\n        <source>Save raw value to file</source>\n        <translation type=\"vanished\">儲存原始值到檔案</translation>\n    </message>\n    <message>\n        <source>Save formatted value to file</source>\n        <translation type=\"vanished\">儲存格式化的值到檔案</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/SaveToFileButton.qml\" line=\"51\"/>\n        <source>Value was saved to file:</source>\n        <translation>已儲存值到檔案:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/bulk-operations/operations/abstractoperation.cpp\" line=\"38\"/>\n        <source>Cannot connect to redis-server</source>\n        <translation>無法連線到 Redis 伺服器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"13\"/>\n        <source>Edit Connection Group</source>\n        <translation>編輯連線群組</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections-tree/menu/server_group.qml\" line=\"17\"/>\n        <source>Delete Connection Group</source>\n        <translation>刪除連線群組</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/servergroup.cpp\" line=\"58\"/>\n        <source>Do you really want to delete group &lt;b&gt;with all connections&lt;/b&gt;?</source>\n        <translation>您真的要刪除群組&lt;b&gt;以及其中的連線&lt;/b&gt;嗎？</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"8\"/>\n        <source>Order of elements:</source>\n        <translation>元素排序:</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"20\"/>\n        <source>Default</source>\n        <translation>預設</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/ListFilters.qml\" line=\"21\"/>\n        <source>Reverse</source>\n        <translation>倒序</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"28\"/>\n        <source>Start date should be less than End date</source>\n        <translation>起始日期必須早於終止日期</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/filters/StreamFilters.qml\" line=\"136\"/>\n        <source>Apply filter</source>\n        <translation>套用篩選器</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"19\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"25\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"49\"/>\n        <source>Trial is active till</source>\n        <translation>試用到</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"58\"/>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"59\"/>\n        <source>Licensed to</source>\n        <translation>授權給</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"66\"/>\n        <source>Subscription is active until:</source>\n        <translation>訂閱到:</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/api/qml/SubscriptionInfo.qml\" line=\"71\"/>\n        <source>Manage Subscription</source>\n        <translation>管理訂閱</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/WelcomeTab.qml\" line=\"30\"/>\n        <source>&lt;span style=&quot;font-size: 11px;&quot;&gt;Powered by awesome &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;open-source software&lt;/a&gt; and &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt;.&lt;/span&gt;</source>\n        <translation>&lt;span style=&quot;font-size: 11px;&quot;&gt;由卓越的 &lt;a href=&quot;https://github.com/uglide/RedisDesktopManager/tree/2021/3rdparty&quot;&gt;開源軟體&lt;/a&gt; 以及 &lt;a href=&quot;http://icons8.com/&quot;&gt;icons8&lt;/a&gt; 驅動。&lt;/span&gt;</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"11\"/>\n        <source>Getting Started</source>\n        <translation>入門指南</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"42\"/>\n        <source>Thank you for choosing RESP.app. Let&apos;s make your Redis experience better.</source>\n        <translation>感謝您選用 RESP.app 。我們一起讓 Redis 有更好的使用體驗吧！</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"60\"/>\n        <source>Connect to Redis-Server</source>\n        <translation>連線到 Redis 伺服器</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/QuickStartDialog.qml\" line=\"73\"/>\n        <source>Read the Docs</source>\n        <translation>閱讀文件</translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/loadmoreitem.cpp\" line=\"12\"/>\n        <source>Load more keys</source>\n        <translation>載入更多鍵</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"24\"/>\n        <source>Yes</source>\n        <translation>是</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/BetterMessageDialog.qml\" line=\"32\"/>\n        <source>No</source>\n        <translation>否</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"19\"/>\n        <source>SSH Passphrase</source>\n        <translation>SSH 密詞</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"21\"/>\n        <source>Unknown</source>\n        <translation>未知</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"46\"/>\n        <location filename=\"../../qml/connections/ConnectionSettignsDialog.qml\" line=\"717\"/>\n        <source>Passphrase</source>\n        <translation>密碼</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/connections/AskSecretDialog.qml\" line=\"71\"/>\n        <source>Continue</source>\n        <translation>繼續</translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/common/ColorInput.qml\" line=\"43\"/>\n        <source>Select</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../qml/value-editor/editors/UnsupportedDataType.qml\" line=\"24\"/>\n        <source>Unsupported Redis Data type </source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <location filename=\"../../modules/connections-tree/items/keyitem.cpp\" line=\"163\"/>\n        <source>Cannot delete key:\n\n</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "src/resp.pro",
    "content": "#-------------------------------------------------\n#\n# RESP.app (formerly Redis Desktop Manager)\n#\n#-------------------------------------------------\n\nCCACHE_BIN = $$system(which ccache)\n!isEmpty(CCACHE_BIN) {\n  load(ccache)\n  CONFIG+=ccache\n}\n\nQT += core gui network concurrent widgets quick quickwidgets charts svg\n\nTARGET = resp\nTEMPLATE = app\n\n!defined(VERSION, var) {\n    VERSION=2022.0.0-dev\n}\n\nmessage($$VERSION)\nDEFINES += APP_VERSION=\\\\\\\"$$VERSION\\\\\\\"\n\nSOURCES += \\\n    $$PWD/main.cpp \\\n    $$PWD/app/app.cpp \\    \n    $$PWD/app/events.cpp \\\n    $$PWD/app/qmlutils.cpp \\\n    $$PWD/app/jsonutils.cpp \\\n    $$PWD/app/qcompress.cpp \\\n    $$files($$PWD/app/models/*.cpp) \\\n    $$files($$PWD/app/models/key-models/*.cpp) \\\n    $$files($$PWD/modules/connections-tree/*.cpp) \\\n    $$files($$PWD/modules/connections-tree/items/*.cpp) \\\n    $$files($$PWD/modules/console/*.cpp) \\\n    $$files($$PWD/modules/value-editor/*model.cpp) \\\n    $$files($$PWD/modules/value-editor/embedded*.cpp) \\\n    $$files($$PWD/modules/value-editor/textcharformat.cpp) \\\n    $$files($$PWD/modules/value-editor/syntaxhighlighter.cpp) \\\n    $$files($$PWD/modules/bulk-operations/*.cpp) \\\n    $$files($$PWD/modules/bulk-operations/operations/*.cpp) \\\n    $$files($$PWD/modules/common/*.cpp) \\\n    $$files($$PWD/modules/server-actions/*.cpp) \\\n\nHEADERS  += \\\n    $$PWD/app/app.h \\\n    $$PWD/app/events.h \\\n    $$PWD/app/apputils.h \\\n    $$PWD/app/qmlutils.h \\\n    $$PWD/app/jsonutils.h \\\n    $$PWD/app/qcompress.h \\\n    $$PWD/app/darkmode.h \\\n    $$files($$PWD/app/models/*.h) \\\n    $$files($$PWD/app/models/key-models/*.h) \\\n    $$files($$PWD/modules/connections-tree/*.h) \\\n    $$files($$PWD/modules/connections-tree/items/*.h) \\\n    $$files($$PWD/modules/console/*.h) \\\n    $$files($$PWD/modules/value-editor/*factory.h) \\\n    $$files($$PWD/modules/value-editor/*model.h) \\\n    $$files($$PWD/modules/value-editor/embedded*.h) \\\n    $$files($$PWD/modules/value-editor/textcharformat.h) \\\n    $$files($$PWD/modules/value-editor/syntaxhighlighter.h) \\\n    $$files($$PWD/modules/*.h) \\\n    $$files($$PWD/modules/bulk-operations/*.h) \\\n    $$files($$PWD/modules/bulk-operations/operations/*.h) \\\n    $$files($$PWD/modules/common/*.h) \\\n    $$files($$PWD/modules/server-actions/*.h) \\\n    $$PWD/modules/connections-tree/items/loadmoreitem.h\n\nTHIRDPARTYDIR = $$PWD/../3rdparty/\n\ninclude($$THIRDPARTYDIR/3rdparty.pri)\n\nexists( $$PWD/modules/crashpad/crashpad.pri ) {\n    message(\"Build with Crashpad\")\n    include($$PWD/modules/crashpad/crashpad.pri)\n}\n\nrelease {\n    message(\"Enable qtquickcompiler\")\n    CONFIG += qtquickcompiler\n}\n\nwin32 {\n    CONFIG += c++11\n\n    RC_ICONS = $$PWD/resources/images/logo.ico\n    QMAKE_TARGET_COMPANY = resp.app\n    QMAKE_TARGET_PRODUCT = RESP\n    QMAKE_TARGET_DESCRIPTION = \"RESP.app - Open source Developer GUI for Redis®\"\n    QMAKE_TARGET_COPYRIGHT = \"Igor Malinovskiy (C) 2013-2022\"\n\n    release: DESTDIR = ./../bin/windows/release\n    debug:   DESTDIR = ./../bin/windows/debug\n\n    LIBS +=  -ldwmapi\n}\n\nunix:macx { # OSX\n    TARGET = \"RESP\"\n    QT += svg\n    CONFIG += c++11\n\n    debug: CONFIG-=app_bundle\n\n    release: DESTDIR = ./../bin/osx/release\n    debug:   DESTDIR = ./../bin/osx/debug\n\n    #deployment\n    QMAKE_INFO_PLIST =  $$PWD/resources/Info.plist\n    ICON = $$PWD/resources/logo.icns\n}\n\nunix:!macx { # ubuntu & debian\n    CONFIG += static release\n    CONFIG -= debug\n\n    DEFINES += DISABLE_SCALING_TEST\n\n    QTPLUGIN += qsvg qsvgicon\n\n    QMAKE_CXXFLAGS += -Wno-sign-compare    \n\n    release: DESTDIR = $$PWD/../bin/linux/release\n    debug:   DESTDIR = $$PWD/../bin/linux/debug\n\n    #deployment\n    LINUX_INSTALL_PATH = /opt/resp_app\n    \n    target.path = $$LINUX_INSTALL_PATH\n    target.files = $$DESTDIR/resp\n    INSTALLS += target\n    \n    exists( $$PWD/resources/qt.conf ) {\n       appconfig.path = $$LINUX_INSTALL_PATH\n       appconfig.files = $$PWD/resources/qt.conf\n       INSTALLS += appconfig\n    }\n    \n    data.path = $$LINUX_INSTALL_PATH/lib\n    data.files = $$PWD/lib/*\n    INSTALLS += data\n    \n    appicon.path = /usr/share/pixmaps/\n    appicon.files = $$PWD/resources/images/resp.png\n    INSTALLS += appicon\n\n    deskicon.path = /usr/share/applications\n    deskicon.files =  $$PWD/resources/resp.desktop\n    INSTALLS += deskicon\n\n    RESOURCES += $$PWD/resources/fonts.qrc\n}\n\nUI_DIR = $$DESTDIR/ui\nOBJECTS_DIR = $$DESTDIR/obj\nMOC_DIR = $$DESTDIR/obj\nRCC_DIR = $$DESTDIR/obj\n\nINCLUDEPATH += $$PWD/ \\\n    $$PWD/modules/ \\\n    $$UI_DIR/ \\\n\nRESOURCES += \\\n    $$PWD/resources/images.qrc \\\n    $$PWD/resources/icons.qrc \\\n    $$PWD/qml/qml.qrc \\\n    $$PWD/py/py.qrc \\\n    $$PWD/resources/commands.qrc\n\nexists( $$PWD/resources/translations/rdm.qm ) {\n    message(\"Translations found\")\n    RESOURCES += $$PWD/resources/tr.qrc\n}\n\nOTHER_FILES += \\\n    qt.conf \\\n    Info.plist \\\n    qml\\*.qml \\\n\n\nlupdate_only{\n    SOURCES += \\\n        $$PWD/qml/*.qml \\\n        $$PWD/qml/value-editor/*.qml \\\n        $$PWD/qml/settings/*.qml \\\n        $$PWD/qml/server-actions/*.qml \\\n        $$PWD/qml/console/*.qml \\\n        $$PWD/qml/connections/*.qml \\\n        $$PWD/qml/connections-tree/*.qml \\\n        $$PWD/qml/common/*.qml \\\n        $$PWD/qml/bulk-operations/*.qml \\\n        $$PWD/qml/extension-server/*.qml \\\n}\n\n\nTRANSLATIONS = \\\n    $$PWD/resources/translations/rdm.ts \\\n    $$PWD/resources/translations/rdm_zh_CN.ts \\\n    $$PWD/resources/translations/rdm_zh_TW.ts \\\n    $$PWD/resources/translations/rdm_es_ES.ts \\\n    $$PWD/resources/translations/rdm_ja_JP.ts \\\n    $$PWD/resources/translations/rdm_uk_UA.ts \\\n\nCODECFORSRC = UTF-8\n"
  },
  {
    "path": "tests/py_tests/requirements.txt",
    "content": "ddt\nnose\n"
  },
  {
    "path": "tests/py_tests/test_formatters/test_msgpack_formatter.py",
    "content": "import io\nimport json\nimport unittest\n\nfrom ddt import ddt, data\nimport msgpack\n\nfrom src.py.formatters.msgpack import MsgpackFormatter\n\n\n@ddt\nclass TestMsgpackFormatter(unittest.TestCase):\n    formatter = MsgpackFormatter()\n\n    @data(\n        [1, 2, 3, 4],\n        [0, [25, 636905376000000000, 636906075333708700, 55.0, None]],\n        [3925794820, 0, msgpack.Timestamp(seconds=1587539993,\n                                          nanoseconds=518021200), False],\n        [1, msgpack.ExtType(code=1, data=b'text')],\n        [1, msgpack.ExtType(code=1, data=b'\\x94\\x01\\x02\\x03\\x04')],\n\n        # Example from #4781\n        b'\\xdc\\x00\\x1f\\xcd9\\x07\\x10\\xcf\\x08\\xd8\\x03\\x9e\\x8f\\xf53t\\xcd\\x01\\xae'\n        b'\\xce\\x00\\x08\\x82:\\xa47626\\xcc\\xca&\\xcfH\\xd8\\x03\\xa1\\xc4C^\\xba\\xcfH'\n        b'\\xd8\\x03\\x9e\\x90\\x81\\xbf\\x01G\\xc2\\xcd\\x05C\\xce\\x00\\x02|Z\\xc2\\xc2\\xcc'\n        b'\\xe5\\xcb@kD\\xc97r\\x82+\\x0b\\xcb@S\\xc0\\x00\\x00\\x00\\x00\\x00\\xa11'\n        b'\\xb4hhjjk c \\xd0\\xbf\\xd0\\xbe\\xd1\\x87\\xd1\\x82\\xd0\\xbe\\xd0\\xb9\\x01\\x85'\n        b'\\x04\\xce\\x00\\x01\\x98\\xa9\\x03\\xce\\x00\\x02\\xdfm\\x02\\xce\\x00\\x02\\xbc]'\n        b'\\x01\\xce\\x00\\x02\\xa9Q\\x00\\xce\\x00\\x0b\\x1b\\xc5\\xcd\\x16n\\xc0\\xc0\\xc2'\n        b'\\xc0\\x92\\x00\\x91\\x0b\\xc0'\n    )\n    def test_decode(self, val):\n        if type(val) == bytes:\n            msgpacked_val = val\n            val = msgpack.loads(val, raw=False, strict_map_key=False)\n        else:\n            msgpacked_val = msgpack.dumps(val)\n\n        expected_output = json.dumps(val, default=self.formatter.default,\n                                     ensure_ascii=False)\n\n        formatter_response_dict = self.formatter.decode(msgpacked_val)\n\n        self.assertIn('output', formatter_response_dict)\n\n        actual_output = formatter_response_dict['output']\n        self.assertEqual(actual_output, expected_output)\n\n    @data(\n        {'valid': ['valid', 'bytes'], 'extra': ['extra', 'bytes']},\n        {'valid': [], 'extra': ['extra', 'bytes']},\n        {'valid': msgpack.Timestamp(1, 1), 'extra': ['extra', 'bytes']},\n    )\n    def test_decode_stream(self, stream):\n        expected_output = json.dumps(stream['valid'],\n                                     default=self.formatter.default,\n                                     ensure_ascii=False)\n        buf = io.BytesIO()\n\n        buf.write(msgpack.dumps(stream['valid'], use_bin_type=True))\n        buf.write(msgpack.dumps(stream['extra'], use_bin_type=True))\n\n        broken_val = buf.getvalue()[:-5]\n        formatter_response_dict = self.formatter.decode(broken_val)\n\n        self.assertIn('output', formatter_response_dict)\n\n        actual_output = formatter_response_dict['output']\n        self.assertEqual(actual_output, expected_output)\n\n    def test_encode(self):\n        val = json.dumps('test')\n        expected_output = msgpack.dumps('test')\n\n        output = self.formatter.encode(val)\n        self.assertEqual(output, expected_output)\n"
  },
  {
    "path": "tests/py_tests/test_formatters/test_php_formatter.py",
    "content": "import json\nimport unittest\n\nfrom ddt import ddt, data\nimport phpserialize\n\nfrom src.py.formatters.phpserialize import PhpSerializeFormatter\n\n\n@ddt\nclass TestPhpSerializeFormatter(unittest.TestCase):\n    formatter = PhpSerializeFormatter()\n\n    @data(\n        'test',\n        {'a': 1, 'b': 'ъъъ', 'c': None, 'd': '✓', 'e': {'f': {'g': '🔫',\n                                                              'h': '喂'}}},\n        # Example from #4789\n        b'O:8:\"stdClass\":2:{s:3:\"foo\";s:3:\"bar\";s:3:\"bar\";s:3:\"baz\";}',\n\n        # Example from #4942\n        b'cookieInfo|i:1;isWholesale|i:0;storageOnly|i:1;isLogged|i:0;'\n        b'itemListType|s:3:\"std\";itemListNum|i:64;search|a:1:{s:5:\"group\";'\n        b's:1:\"2\";}sr22|a:2:{s:3:\"sex\";s:7:\"0,1,2,3\";s:4:\"size\";s:68:'\n        b'\"17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,'\n        b'37,38,39\";}'\n\n        # Foo(foo=Bar(bar=Foo(foo={'a': 1, 'b': 2}), open=False))\n        b'O:3:\"Foo\":1:{s:3:\"foo\";O:3:\"Bar\":2:{s:3:\"bar\";O:3:\"Foo\":1:{s:3:\"foo\";'\n        b'a:2:{s:1:\"a\";i:1;s:1:\"b\";i:2;}}s:4:\"open\";b:0;}}',\n\n        # Foo(foo=Bar(bar='baz', open=True))\n        b'O:3:\"Foo\":1:{s:3:\"foo\";O:3:\"Bar\":2:{s:3:\"bar\";s:3:\"baz\";s:4:\"open\";'\n        b'b:1;}}'\n\n        # Foo(foo=Bar(bar=[1, 2], open=True))\n        b'O:3:\"Foo\":1:{s:3:\"foo\";O:3:\"Bar\":2:{s:3:\"bar\";a:2:{i:0;i:1;i:1;i:2;}'\n        b's:4:\"open\";b:1;}}'\n    )\n    def test_decode(self, val):\n        if type(val) == bytes:\n            serialized_val = val\n            val = phpserialize.loads(val, decode_strings=True,\n                                     object_hook=phpserialize.phpobject)\n            if not isinstance(val, dict):\n                val = val._asdict()\n        else:\n            serialized_val = phpserialize.dumps(val)\n        expected_output = json.dumps(val, ensure_ascii=False,\n                                     default=self.formatter.default)\n\n        formatter_response_dict = self.formatter.decode(serialized_val)\n\n        self.assertIn('output', formatter_response_dict)\n\n        actual_output = formatter_response_dict['output']\n        self.assertEqual(actual_output, expected_output)\n\n    @data(\n        'test',\n        {'喂': 'test'},\n    )\n    def test_encode(self, val):\n        formatter_input = json.dumps(val, ensure_ascii=False)\n        expected_output = phpserialize.dumps(val)\n\n        output = self.formatter.encode(formatter_input)\n        self.assertEqual(output, expected_output)\n"
  },
  {
    "path": "tests/py_tests/test_formatters/test_pickle_formatter.py",
    "content": "from datetime import datetime\nimport json\nimport pickle\nimport unittest\n\nfrom ddt import ddt, data\nimport numpy as np\nimport pandas as pd\n\n\nfrom src.py.formatters.pickle import PickleFormatter\n\n\n@ddt\nclass TestPickleFormatter(unittest.TestCase):\n    formatter = PickleFormatter()\n\n    @data(\n        [1, 2, 3, 4],\n        datetime.now(),\n        np.array([[1, 2], [3, 4]]),\n        np.array([1, 2, 3], dtype=complex),\n        pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e']),\n        pd.Series({'b': 1, 'a': 0, 'c': 2}),\n        pd.Series({'b': 1, 'a': 0, 'c': 2}, index=['b', 'c', 'd', 'a']),\n        pd.Series(np.random.randn(5)),\n        pd.Series(pd.date_range('20130101', periods=6)),\n        pd.DataFrame(np.random.randn(6, 4),\n                     index=pd.date_range('20130101', periods=6),\n                     columns=list('ABCD')),\n    )\n    def test_decode(self, val):\n        if isinstance(val, pd.Series):\n            expected_output = f'{str(type(val))[1:-1]}\\n{val.to_string()}'\n        elif isinstance(val, pd.DataFrame):\n            html = val.to_html(render_links=True, border=0)\n            expected_output = self.formatter.format_html_output(val, html)\n        elif isinstance(val, np.ndarray):\n            html = pd.DataFrame(val).to_html(render_links=True, border=0)\n            expected_output = self.formatter.format_html_output(val, html)\n        else:\n            expected_output = json.dumps(val, default=self.formatter.default,\n                                         ensure_ascii=False)\n\n        pickled_val = pickle.dumps(val)\n        formatter_response_dict = self.formatter.decode(pickled_val)\n\n        self.assertIn('output', formatter_response_dict)\n\n        actual_output = formatter_response_dict['output']\n        self.assertEqual(actual_output, expected_output)\n"
  },
  {
    "path": "tests/qml_tests/qml_tests.pro",
    "content": "TEMPLATE = app\nTARGET = qml_tests\n\nCONFIG += warn_on qmltestcase\nCONFIG-=app_bundle\n\nSOURCES += $$PWD/qml_test_runner.cpp \\\n    setup.cpp\n\nPROJECT_ROOT = $$PWD/../..//\n\nINCLUDEPATH += $$PROJECT_ROOT/src $$PROJECT_ROOT/src/modules $$PROJECT_ROOT/3rdparty/qredisclient/src\nSOURCES += \\\n    $$files($$PROJECT_ROOT/src/app/apputils.cpp) \\\n    $$files($$PROJECT_ROOT/src/app/qmlutils.cpp) \\\n    $$files($$PROJECT_ROOT/src/app/jsonutils.cpp) \\\n    $$files($$PROJECT_ROOT/src/app/qcompress.cpp) \\\n    $$files($$PROJECT_ROOT/src/modules/value-editor/textcharformat.cpp) \\\n    $$files($$PROJECT_ROOT/src/modules/value-editor/syntaxhighlighter.cpp) \\\n    $$files($$PROJECT_ROOT/src/modules/value-editor/largetextmodel.cpp) \\\n\nQT += core gui quick network concurrent charts\n\nrelease: DESTDIR = $$PROJECT_ROOT/bin/tests\ndebug:   DESTDIR = $$PROJECT_ROOT/bin/tests\n\nOBJECTS_DIR = $$DESTDIR/qml_obj\nMOC_DIR = $$DESTDIR/qml_obj\nRCC_DIR = $$DESTDIR/qml_obj\n\nOTHER_FILES = $$PWD/tst_*.qml\n\nHEADERS += \\\n    setup.h \\\n    $$files($$PROJECT_ROOT/src/app/qmlutils.h) \\\n    $$files($$PROJECT_ROOT/src/modules/value-editor/textcharformat.h) \\\n    $$files($$PROJECT_ROOT/src/modules/value-editor/syntaxhighlighter.h) \\\n    $$files($$PROJECT_ROOT/src/modules/value-editor/largetextmodel.h) \\\n\nDISTFILES += \\\n    tst_MultilineEditor.qml\n\ninclude($$PROJECT_ROOT/3rdparty/3rdparty.pri)\n"
  },
  {
    "path": "tests/qml_tests/setup.cpp",
    "content": "#include \"setup.h\"\n\n#include <QSettings>\n\n#include \"modules/value-editor/syntaxhighlighter.h\"\n#include \"modules/value-editor/textcharformat.h\"\n\nvoid Setup::qmlEngineAvailable(QQmlEngine *engine)\n{\n    QCoreApplication::instance()->setOrganizationDomain(\"redisdesktop.com\");\n    QCoreApplication::instance()->setOrganizationName(\"redisdesktop\");\n    QCoreApplication::instance()->setApplicationName(\"RESP.app - Developer GUI for Redis\");\n\n    qmlRegisterType<SyntaxHighlighter>(\"rdm.models\", 1, 0, \"SyntaxHighlighter\");\n    qmlRegisterType<TextCharFormat>(\"rdm.models\", 1, 0, \"TextCharFormat\");\n\n    m_qmlUtils = QSharedPointer<QmlUtils>(new QmlUtils());\n    engine->rootContext()->setContextProperty(\"qmlUtils\", m_qmlUtils.data());\n\n    m_testUtils = QSharedPointer<TestUtils>(new TestUtils());\n    engine->rootContext()->setContextProperty(\"testUtils\", m_testUtils.data());\n}\n\nvoid TestUtils::removeAppSetting(const QString &category)\n{\n    QSettings s;\n    s.remove(category);\n}\n"
  },
  {
    "path": "tests/qml_tests/setup.h",
    "content": "#pragma once\n\n#include <QtQuickTest>\n#include <QQmlEngine>\n#include <QQmlContext>\n#include <QSharedPointer>\n#include <QQuickTextDocument>\n#include \"app/qmlutils.h\"\n\nclass TestUtils : public QObject\n{\n    Q_OBJECT\npublic:\n    Q_INVOKABLE void removeAppSetting(const QString& category);\n};\n\nclass Setup : public QObject\n{\n    Q_OBJECT\npublic:\n    Setup() {}\n\npublic slots:\n    void qmlEngineAvailable(QQmlEngine *engine);\n\nprivate:\n    QSharedPointer<QmlUtils> m_qmlUtils;\n    QSharedPointer<TestUtils> m_testUtils;\n};\n"
  },
  {
    "path": "tests/qml_tests/tst_MultilineEditor.qml",
    "content": "import QtQuick 2.3\nimport QtQml.Models 2.13\nimport QtTest 1.0\nimport Qt.labs.settings 1.0\nimport rdm.models 1.0\n\nimport \"./../../src/qml/value-editor/editors/formatters/\"\nimport \"./../../src/qml/value-editor/editors/\"\n\nTestCase {\n    name: \"FormatterTests\"\n\n    property string validJson: '{\"test\": 123}'\n\n    SystemPalette {\n        id: sysPalette\n    }\n\n    Settings {\n        id: defaultFormatterSettings\n        category: \"formatter_overrides\"\n\n        function cleanup() {\n            testUtils.removeAppSetting(\"formatter_overrides\")\n            defaultFormatterSettings.sync()\n        }\n    }\n\n    Item {\n        id: appSettings\n\n        property string valueEditorFont: \"sans-serif\"\n    }\n\n    ValueFormatters {\n        id: valueFormattersModel\n    }\n\n    QtObject {\n        id: valueEditor\n\n        property var item: QtObject {\n            function isEdited() {\n                return false;\n            }\n        }\n    }\n\n    MultilineEditor {\n        id: editor\n\n        // ValueEditor fake properties\n        property bool isMultiRow: true\n        property string keyType: \"list\"\n        property string keyName: \"fake_list\"\n    }\n\n    function init() {\n        defaultFormatterSettings.cleanup()\n        editor.defaultFormatter = \"auto\"\n    }\n\n    function test_loadFormattedValue() {       \n        editor.loadFormattedValue(validJson)\n\n        verify(editor.__formatterCombobox.currentText === \"JSON\")\n        verify(editor.__textView.format === \"json\")\n        verify(!editor.__textView.readOnly)\n        verify(editor.__textView.textFormat === TextEdit.PlainText)\n    }\n\n    function test_loadFormattedValue_withDefaultFormatter() {\n        editor.defaultFormatter = \"HEX TABLE\"\n        defaultFormatterSettings.setValue(editor.lastSelectedFormatterSetting, \"Plain Text\")\n        defaultFormatterSettings.sync()\n\n        editor.loadFormattedValue(validJson)\n\n        verify(editor.__formatterCombobox.currentText === \"HEX TABLE\")\n        verify(editor.__textView.format === \"html\")\n        verify(editor.__textView.readOnly)\n        verify(editor.__textView.textFormat === TextEdit.RichText)\n    }\n\n    function test_loadFormattedValue_withLastSelectedFormatter() {\n        editor.defaultFormatter = \"last_used\"\n        defaultFormatterSettings.setValue(editor.lastSelectedFormatterSetting, \"Plain Text\")\n        defaultFormatterSettings.sync()\n\n        editor.loadFormattedValue(validJson)\n\n        verify(editor.__formatterCombobox.currentText === \"Plain Text\")\n        verify(editor.__textView.format === \"plain\")\n        verify(!editor.__textView.readOnly)\n        verify(editor.__textView.textFormat === TextEdit.PlainText)\n    }\n\n    function test_loadFormattedValue_withLastSelectedFormatter_and_key_override() {\n        defaultFormatterSettings.setValue(editor.keyName, \"HEX\")\n        defaultFormatterSettings.setValue(editor.lastSelectedFormatterSetting, \"Plain Text\")\n        defaultFormatterSettings.sync()\n\n        editor.loadFormattedValue(validJson)\n\n        verify(editor.__formatterCombobox.currentText === \"HEX\")\n        verify(editor.__textView.format === \"plain\")\n        verify(!editor.__textView.readOnly)\n        verify(editor.__textView.textFormat === TextEdit.PlainText)\n    }\n\n}\n"
  },
  {
    "path": "tests/qml_tests/tst_formatters.qml",
    "content": "import QtQuick 2.3\nimport QtQml.Models 2.13\nimport QtTest 1.0\n\nimport \"./../../src/qml/value-editor/editors/formatters/\"\n\nTestCase {\n    name: \"FormatterTests\"\n\n    ValueFormatters {\n        id: valueFormatters\n    }\n\n    function test_plain() {\n        // given\n        var plain = valueFormatters.get(0)\n        var testValue = \"plain_text!\"\n\n        // checks\n        verify(plain.name.length !== 0, \"title\")\n\n        plain.getFormatted(testValue, function (error, formatted, readOnly, format){\n            compare(formatted, testValue)\n        })\n\n        plain.getRaw(testValue, function (error, plain){\n            compare(plain, testValue)\n        })\n    }\n}\n"
  },
  {
    "path": "tests/smoke_test.bat",
    "content": "@echo off\ntaskkill /f /im rdm.exe\ncd build/windows/installer/resources/\nSTART /b rdm.exe\nTIMEOUT 30\ntasklist /FI \"IMAGENAME eq rdm.exe\" 2>NUL | find /I /N \"rdm.exe\">NUL\n\nif \"%ERRORLEVEL%\"==\"0\" (\ntaskkill /f /im rdm.exe\nexit 0\n) ELSE (exit 1)\n"
  },
  {
    "path": "tests/tests.pro",
    "content": "TEMPLATE = subdirs\nSUBDIRS = unit_tests \\\n          qml_tests \\\n"
  },
  {
    "path": "tests/unit_tests/generate_coverage_report",
    "content": "#!/bin/bash\nROOT_DIR=`readlink -f ./../../`\nBASE_DIR=`pwd`\nOBJ_DIR=$ROOT_DIR/bin/tests/obj\n\n#Build\nexport LDFLAGS=\"-lgcov -fprofile-arcs\"\nexport CPPFLAGS=\"-fprofile-arcs -ftest-coverage\"\nrm -fR $ROOT_DIR/bin coverage*\nqmake && make -sj 4\n\n# Generate coverage files for all files\nlcov --base-directory $BASE_DIR --capture --initial --directory $OBJ_DIR --output-file coverage.info.base\n\n# Run tests\n$ROOT_DIR/bin/tests/tests\n\n# Generate coverage files for files used in tests\nlcov --capture --directory $OBJ_DIR --base-directory $BASE_DIR --output-file coverage.info.run\n# Merge result\nlcov -a coverage.info.base -a coverage.info.run -o coverage.info\n\n# Remove unit tests and external files to get clean report\nlcov -e coverage.info \"$ROOT_DIR/*\" -o coverage.info.filtered\nlcov -r coverage.info.filtered \"$ROOT_DIR/tests/*\" -o coverage.info.filtered\nlcov -r coverage.info.filtered \"$ROOT_DIR/bin/*\" -o coverage.info.filtered\nlcov -r coverage.info.filtered \"$ROOT_DIR/3rdparty/*\" -o coverage.info.filtered\n\n# Generate HTML report\nrm -fR coverage_report || true\nmkdir coverage_report\ngenhtml coverage.info.filtered --output-directory coverage_report\nopen coverage_report/index.html\n"
  },
  {
    "path": "tests/unit_tests/main.cpp",
    "content": "#include <QApplication>\n#include <QTest>\n\n// tests\n#include <qredisclient/redisclient.h>\n#include <iostream>\n#include \"testcases/app/test_configmanager.h\"\n#include \"testcases/app/test_connectionsmanager.h\"\n#include \"testcases/app/test_keymodels.h\"\n#include \"testcases/app/test_treeoperations.h\"\n#include \"testcases/app/test_apputils.h\"\n#include \"testcases/connections-tree/test_databaseitem.h\"\n#include \"testcases/connections-tree/test_model.h\"\n#include \"testcases/connections-tree/test_serveritem.h\"\n#include \"testcases/console/test_consolemodel.h\"\n\nint main(int argc, char *argv[]) {\n  QApplication app(argc, argv);\n\n  initRedisClient();\n\n  int allTestsResult = 0\n                       // connections-tree module\n#ifndef Q_OS_WIN\n                       + QTest::qExec(new TestServerItem, argc, argv)\n                       + QTest::qExec(new TestDatabaseItem, argc, argv)\n#endif\n                       + QTest::qExec(new TestModel, argc, argv)\n\n                       // console module\n                       + QTest::qExec(new TestConsoleOperations, argc, argv)\n\n                       // app\n                       + QTest::qExec(new TestConnectionsManager, argc, argv)\n                       + QTest::qExec(new TestConfigManager, argc, argv)\n                       + QTest::qExec(new TestKeyModels, argc, argv)\n                       + QTest::qExec(new TestTreeOperations, argc, argv)\n                       + QTest::qExec(new TestAppUtils, argc, argv)\n                       ;\n\n  if (allTestsResult == 0)\n    qDebug() << \"[Tests PASS]\";\n  else\n    qDebug() << \"[Tests FAIL]\";\n\n  return (allTestsResult != 0) ? 1 : 0;\n}\n"
  },
  {
    "path": "tests/unit_tests/respbasetestcase.h",
    "content": "#pragma once\n#include <QObject>\n\n#include \"basetestcase.h\"\n#include \"models/connectionconf.h\"\n\ntemplate<typename T>\nstatic void fakeDeleter(T*) {}\n\nclass RESPBaseTestCase : public BaseTestCase {\n  Q_OBJECT\n\n protected:\n  ServerConfig getDummyConfig(QString name = \"test\") {\n    ServerConfig dummyConf(\"127.0.0.1\", \"\",\n                           RedisClient::ConnectionConfig ::DEFAULT_REDIS_PORT,\n                           name);\n    dummyConf.setTimeouts(2000, 2000);\n    return dummyConf;\n  }\n};\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/app-tests.pri",
    "content": "\r\nAPP_SRC_DIR = $$PWD/../../../../src/app/\r\n\r\nINCLUDEPATH += $$APP_SRC_DIR\r\n\r\nHEADERS  += \\\r\n    $$files($$PWD/test_*.h) \\\r\n    $$APP_SRC_DIR/events.h \\\r\n    $$APP_SRC_DIR/apputils.h \\\r\n    $$APP_SRC_DIR/jsonutils.h \\\r\n    $$APP_SRC_DIR/models/connectionsmanager.h \\\r\n    $$APP_SRC_DIR/models/configmanager.h \\\r\n    $$APP_SRC_DIR/models/connectionconf.h \\\r\n    $$APP_SRC_DIR/models/connectiongroup.h \\\r\n    $$APP_SRC_DIR/models/treeoperations.h \\\r\n    $$APP_SRC_DIR/models/key-models/keyfactory.h \\\r\n    $$APP_SRC_DIR/models/key-models/abstractkey.h \\\r\n    $$APP_SRC_DIR/models/key-models/stringkey.h \\\r\n    $$APP_SRC_DIR/models/key-models/listkey.h \\\r\n    $$APP_SRC_DIR/models/key-models/listlikekey.h \\\r\n    $$APP_SRC_DIR/models/key-models/setkey.h \\\r\n    $$APP_SRC_DIR/models/key-models/stream.h \\\r\n    $$APP_SRC_DIR/models/key-models/sortedsetkey.h \\\r\n    $$APP_SRC_DIR/models/key-models/hashkey.h \\            \r\n    $$APP_SRC_DIR/models/key-models/rejsonkey.h \\\r\n    $$APP_SRC_DIR/models/key-models/unknownkey.h \\\r\n    $$APP_SRC_DIR/models/key-models/newkeyrequest.h \\\r\n\r\nSOURCES += \\\r\n    $$files($$PWD/test_*.cpp) \\\r\n    $$APP_SRC_DIR/events.cpp \\    \r\n    $$APP_SRC_DIR/jsonutils.cpp \\\r\n    $$APP_SRC_DIR/models/connectionsmanager.cpp \\\r\n    $$APP_SRC_DIR/models/configmanager.cpp \\\r\n    $$APP_SRC_DIR/models/connectiongroup.cpp \\\r\n    $$APP_SRC_DIR/models/connectionconf.cpp \\\r\n    $$APP_SRC_DIR/models/treeoperations.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/keyfactory.cpp \\    \r\n    $$APP_SRC_DIR/models/key-models/stringkey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/listkey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/listlikekey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/setkey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/stream.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/sortedsetkey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/hashkey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/rejsonkey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/unknownkey.cpp \\\r\n    $$APP_SRC_DIR/models/key-models/newkeyrequest.cpp \\\r\n\r\nOTHER_FILES += \\\r\n    $$PWD/connections.json\r\n\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/connections.json",
    "content": "[\n{\"host\":\"127.0.0.1\", \"name\": \"local\", \"port\": 6379}\n]\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_apputils.cpp",
    "content": "#include \"test_apputils.h\"\n\n#include \"app/apputils.h\"\n\nvoid TestAppUtils::testHumanReadableSize() {\n  long long size = 3000000000;\n\n  QString result = humanReadableSize(size);\n\n  QCOMPARE(result, \"3.00 GB\");\n}\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_apputils.h",
    "content": "#pragma once\n\n#include \"respbasetestcase.h\"\n\nclass TestAppUtils : public RESPBaseTestCase\n{\n    Q_OBJECT    \n\nprivate slots:\n    void testHumanReadableSize();\n};\n\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_configmanager.cpp",
    "content": "#include \"test_configmanager.h\"\n#include \"app/models/configmanager.h\"\n\n#include <QTemporaryDir>\n#include <QFile>\n\nvoid TestConfigManager::testGetApplicationConfigPath()\n{\n#ifdef Q_OS_WIN\n    QSKIP(\"SKIP ON Windows\");\n#endif\n    // Given\n    QTemporaryDir tmpDir;\n    tmpDir.setAutoRemove(true);\n    QString basePath = tmpDir.path();\n    ConfigManager manager(basePath);\n    qDebug() << \"Base path:\" << basePath;\n    bool check_path = true;\n\n    // When\n    QString actual_result = manager.getApplicationConfigPath(\"config.json\", check_path);\n\n    // Then\n    // Check that path is valid\n    QCOMPARE(QString(\"%1/.rdm/config.json\").arg(basePath),\n             actual_result);\n    QCOMPARE(check_path, QFile::exists(actual_result));\n}\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_configmanager.h",
    "content": "#pragma once\n#include \"basetestcase.h\"\n\nclass TestConfigManager : public BaseTestCase\n{\n    Q_OBJECT\nprivate slots:\n    void testGetApplicationConfigPath();    \n};\n\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_connectionsmanager.cpp",
    "content": "#include \"test_connectionsmanager.h\"\n#include <QDir>\n#include <QFile>\n#include <QModelIndex>\n#include <QtTest/QtTest>\n#include \"app/events.h\"\n#include \"models/connectionsmanager.h\"\n\nTestConnectionsManager::TestConnectionsManager() {}\n\nvoid TestConnectionsManager::loadConnectionsConfigFromFile() {\n  // given\n  // json fixture\n  QString configTestFile = \"./unit_tests/testcases/app/connections.json\";\n  auto events = QSharedPointer<Events>(new Events());\n\n  // when loads connections\n  ConnectionsManager testManager(configTestFile, events);\n  testManager.loadConnections();\n\n  // then\n  QCOMPARE(testManager.size(), 1);\n}\n\nvoid TestConnectionsManager::saveConnectionsConfigToFile_data() {\n  QTest::addColumn<QString>(\"connectionName\");\n\n  QTest::newRow(\"simple\") << \"test\";\n  QTest::newRow(\"unicode\") << \"❤❤❤༆\";\n}\n\nvoid TestConnectionsManager::saveConnectionsConfigToFile() {\n  // given\n  QFETCH(QString, connectionName);\n  QString configTestFile = QString(\"%1/test_rdm.json\").arg(QDir::tempPath());\n  QFile::remove(configTestFile);\n  auto connectionConfig = getDummyConfig(connectionName);\n  auto events = QSharedPointer<Events>(new Events());\n  ConnectionsManager testManager(configTestFile, events);\n\n  // when\n  // add new connection and save\n  testManager.addNewConnection(connectionConfig, true);\n  // load everything from scratch\n  ConnectionsManager testManagerNew(configTestFile, events);\n  testManagerNew.loadConnections();\n  QModelIndex testIndex = testManagerNew.index(0, 0, QModelIndex());\n  auto metadata =\n      testManagerNew.data(testIndex, ConnectionsTree::Model::itemMetaData).toHash();\n\n  // then  \n  QCOMPARE(QFile::exists(configTestFile), true);\n  QCOMPARE(testManagerNew.rowCount(), 1);\n  QCOMPARE(metadata[\"name\"], QVariant(connectionName));\n}\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_connectionsmanager.h",
    "content": "#pragma once\n#include \"respbasetestcase.h\"\n#include \"value-editor/tabsmodel.h\"\n\nclass TestConnectionsManager : public RESPBaseTestCase\n{\n\tQ_OBJECT\n\t\npublic:\n    TestConnectionsManager();\n\nprivate slots:\n\tvoid loadConnectionsConfigFromFile();\n\t\n\tvoid saveConnectionsConfigToFile();\n    void saveConnectionsConfigToFile_data();\n};\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_keymodels.cpp",
    "content": "#include \"test_keymodels.h\"\r\n\r\n#include \"app/models/key-models/hashkey.h\"\r\n#include \"app/models/key-models/listkey.h\"\r\n#include \"app/models/key-models/setkey.h\"\r\n#include \"app/models/key-models/sortedsetkey.h\"\r\n#include \"app/models/key-models/stringkey.h\"\r\n\r\nvoid TestKeyModels::testKeyFactory() {\r\n  // given\r\n  QFETCH(QStringList, validReplies);\r\n  auto dummyConnection = getRealConnectionWithDummyTransporter(validReplies);\r\n\r\n  // when\r\n  QSharedPointer<ValueEditor::Model> actualResult =\r\n      getKeyModel(dummyConnection);\r\n\r\n  // then\r\n  QFETCH(QString, typeValid);\r\n  QFETCH(int, ttlValid);\r\n  QCOMPARE(actualResult.isNull(), false);\r\n  QCOMPARE(actualResult->type(), typeValid);\r\n  QCOMPARE(actualResult->getTTL(), ttlValid);\r\n}\r\n\r\nvoid TestKeyModels::testKeyFactory_data() {\r\n  QTest::addColumn<QStringList>(\"validReplies\");\r\n  QTest::addColumn<QString>(\"typeValid\");\r\n  QTest::addColumn<int>(\"ttlValid\");\r\n\r\n  QTest::newRow(\"Valid string model w/o TTL\") << (QStringList() << \"+string\\r\\n\"\r\n                                                                << \":-1\\r\\n\")\r\n                                              << \"string\" << -1;\r\n\r\n  QTest::newRow(\"Valid string model w TTL\") << (QStringList() << \"+string\\r\\n\"\r\n                                                              << \":100\\r\\n\")\r\n                                            << \"string\" << 100;\r\n\r\n  QTest::newRow(\"Valid list model w/o TTL\") << (QStringList() << \"+list\\r\\n\"\r\n                                                              << \":-1\\r\\n\"\r\n                                                              << \":1\\r\\n\")\r\n                                            << \"list\" << -1;\r\n\r\n  QTest::newRow(\"Valid set model w/o TTL\") << (QStringList() << \"+set\\r\\n\"\r\n                                                             << \":-1\\r\\n\"\r\n                                                             << \":1\\r\\n\")\r\n                                           << \"set\" << -1;\r\n\r\n  QTest::newRow(\"Valid sorted set model w/o TTL\")\r\n      << (QStringList() << \"+zset\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \":1\\r\\n\")\r\n      << \"zset\" << -1;\r\n\r\n  QTest::newRow(\"Valid hash model w/o TTL\") << (QStringList() << \"+hash\\r\\n\"\r\n                                                              << \":-1\\r\\n\"\r\n                                                              << \":1\\r\\n\")\r\n                                            << \"hash\" << -1;\r\n}\r\n\r\nvoid TestKeyModels::testKeyFactoryAddKey() {\r\n  // given\r\n  QFETCH(QStringList, testReplies);\r\n  QFETCH(QString, keyType);\r\n  QFETCH(QVariantMap, row);\r\n  auto connection = getRealConnectionWithDummyTransporter(testReplies);\r\n  KeyFactory factory;\r\n  NewKeyRequest r(connection, -1, QSharedPointer<ConnectionsTree::Operations::OpenNewKeyDialogCallback>());\r\n\r\n  // when\r\n  r.setKeyName(\"testKey\");\r\n  r.setKeyType(keyType);\r\n  r.setValue(row);\r\n  factory.submitNewKeyRequest(r);\r\n  wait(100);\r\n\r\n  // then\r\n  verifyExecutedCommandsCount(connection,\r\n                              testReplies.size() + 2);  // 2 = ping + info\r\n}\r\n\r\nvoid TestKeyModels::testKeyFactoryAddKey_data() {\r\n  QTest::addColumn<QStringList>(\"testReplies\");\r\n  QTest::addColumn<QString>(\"keyType\");\r\n  QTest::addColumn<QVariantMap>(\"row\");\r\n\r\n  QVariantMap singleRow{{\"value\", \"test\"}};\r\n  QTest::newRow(\"string\") << (QStringList() << \"+OK\\r\\n\") << \"string\"\r\n                          << singleRow;\r\n  QTest::newRow(\"list\") << (QStringList() << \"+OK\\r\\n\") << \"list\" << singleRow;\r\n  QTest::newRow(\"set\") << (QStringList() << \"+OK\\r\\n\") << \"set\" << singleRow;\r\n\r\n  QVariantMap hashRow{{\"value\", \"test\"}, {\"key\", \"test-key\"}};\r\n  QTest::newRow(\"hash\") << (QStringList() << \":1\\r\\n\") << \"hash\" << hashRow;\r\n\r\n  QVariantMap zsetRow{{\"value\", \"test\"}, {\"score\", 5.0}};\r\n  QTest::newRow(\"zset\") << (QStringList() << \"+OK\\r\\n\") << \"zset\" << zsetRow;\r\n}\r\n\r\nvoid TestKeyModels::testValueLoading() {\r\n  // given\r\n  QFETCH(QStringList, testReplies);\r\n  auto dummyConnection = getRealConnectionWithDummyTransporter(testReplies);\r\n\r\n  QFETCH(int, testRow);\r\n  QFETCH(int, testRole);\r\n  QFETCH(unsigned long, validRowCount);\r\n  QFETCH(bool, validIsMultiRow);\r\n\r\n  // when\r\n  QSharedPointer<ValueEditor::Model> keyModel = getKeyModel(dummyConnection);\r\n  QVERIFY(keyModel.isNull() == false);\r\n  QVERIFY(keyModel->isMultiRow() == validIsMultiRow);\r\n\r\n  bool callbackCalled = false;\r\n\r\n  keyModel->loadRowsCount([keyModel, &callbackCalled, validRowCount](QString) {\r\n    QVERIFY(keyModel->rowsCount() == validRowCount);\r\n\r\n    keyModel->loadRows(0, keyModel->rowsCount(),\r\n                       [&callbackCalled](const QString&, unsigned long) {\r\n                         callbackCalled = true;\r\n                       });\r\n  });\r\n\r\n  wait(500);\r\n  QVERIFY(callbackCalled);\r\n  QVERIFY(keyModel->isRowLoaded(testRow));\r\n\r\n  QVariant actualResult = keyModel->getData(testRow, testRole);\r\n  keyModel->clearRowCache();\r\n\r\n  // then\r\n  QFETCH(QString, validData);\r\n  QFETCH(QStringList, validColumns);\r\n  QCOMPARE(actualResult.toString(), validData);\r\n  QCOMPARE(keyModel->getColumnNames(), validColumns);\r\n  QVERIFY(keyModel->getRoles().size() != 0);\r\n  QVERIFY(keyModel->isRowLoaded(0) == false);\r\n}\r\n\r\nvoid TestKeyModels::testValueLoading_data() {\r\n  QTest::addColumn<QStringList>(\"testReplies\");\r\n  QTest::addColumn<int>(\"testRow\");\r\n  QTest::addColumn<int>(\"testRole\");\r\n  QTest::addColumn<unsigned long>(\"validRowCount\");\r\n  QTest::addColumn<bool>(\"validIsMultiRow\");\r\n  QTest::addColumn<QString>(\"validData\");\r\n  QTest::addColumn<QStringList>(\"validColumns\");\r\n\r\n  QTest::newRow(\"Valid string model\")\r\n      << (QStringList() << \"+string\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \"$17\\r\\n__nice_test_data!\\r\\n\")\r\n      << 0 << Qt::UserRole + 1 << (unsigned long)1 << false\r\n      << \"__nice_test_data!\" << (QStringList() << \"value\");\r\n\r\n  QTest::newRow(\"Valid list model\")\r\n      << (QStringList() << \"+list\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \":2\\r\\n\"\r\n                        << \"*2\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\")\r\n      << 1 << Qt::UserRole + 2 << (unsigned long)2 << true << \"bar\"\r\n      << (QStringList() << \"rowNumber\"\r\n                        << \"value\");\r\n\r\n  QTest::newRow(\"Valid set model\")\r\n      << (QStringList() << \"+set\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \":2\\r\\n\"\r\n                        << \"*2\\r\\n$1\\r\\n0\\r\\n*2\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\"\r\n                        << \":1\\r\\n\"\r\n                        << \":1\\r\\n\"\r\n                        << \":1\\r\\n\"\r\n                        << \":1\\r\\n\")\r\n      << 1 << Qt::UserRole + 2 << (unsigned long)2 << true << \"bar\"\r\n      << (QStringList() << \"rowNumber\"\r\n                        << \"value\");\r\n\r\n  QTest::newRow(\"Valid zset model\")\r\n      << (QStringList()\r\n          << \"+zset\\r\\n\"\r\n          << \":-1\\r\\n\"\r\n          << \":2\\r\\n\"\r\n          << \"*4\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$3\\r\\nbar\\r\\n$1\\r\\n1\\r\\n\")\r\n      << 1 << Qt::UserRole + 2 << (unsigned long)2 << true << \"bar\"\r\n      << (QStringList() << \"rowNumber\"\r\n                        << \"value\"\r\n                        << \"score\");\r\n\r\n  QTest::newRow(\"Valid hash model\")\r\n      << (QStringList() << \"+hash\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \":2\\r\\n\"\r\n                        << \"*2\\r\\n$1\\r\\n0\\r\\n*4\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$\"\r\n                           \"3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\")\r\n      << 1 << Qt::UserRole + 3 << (unsigned long)2 << true << \"bar\"\r\n      << (QStringList() << \"rowNumber\"\r\n                        << \"key\"\r\n                        << \"value\");\r\n}\r\n\r\nvoid TestKeyModels::testKeyModelModifyRows() {\r\n  // given\r\n  QFETCH(QStringList, testReplies);\r\n  QFETCH(QVariantMap, row);\r\n  QFETCH(int, role);\r\n  bool rowsCountLoaded = false;\r\n  auto dummyConnection = getRealConnectionWithDummyTransporter(testReplies);\r\n\r\n  // when\r\n  QSharedPointer<ValueEditor::Model> keyModel = getKeyModel(dummyConnection);\r\n  QVERIFY(keyModel.isNull() == false);\r\n  keyModel->loadRowsCount([keyModel, &rowsCountLoaded](QString) {\r\n    rowsCountLoaded = true;\r\n\r\n    keyModel->loadRows(0, 10, [](const QString& err, unsigned long) {\r\n      if (!err.isEmpty()) {\r\n        qWarning() << err;\r\n        return;\r\n      }\r\n    });\r\n  });\r\n  wait(500);\r\n\r\n  row[\"value\"] = \"fakeUpdate\";\r\n  keyModel->updateRow(0, row, [](const QString& err) {\r\n    if (!err.isEmpty()) {\r\n      qWarning() << err;\r\n    }\r\n  });\r\n  wait(500);\r\n\r\n  QVariant actualResult = keyModel->getData(0, role);\r\n\r\n  // then\r\n  QVERIFY(rowsCountLoaded);\r\n  QVERIFY(actualResult.type() == QVariant::ByteArray);\r\n  QCOMPARE(actualResult.toString(), QString(\"fakeUpdate\"));\r\n}\r\n\r\nvoid TestKeyModels::testKeyModelModifyRows_data() {\r\n  QTest::addColumn<QStringList>(\"testReplies\");\r\n  QTest::addColumn<QVariantMap>(\"row\");\r\n  QTest::addColumn<int>(\"role\");\r\n\r\n  QVariantMap stringRow;\r\n  stringRow[\"value\"] = \"test\";\r\n  QTest::newRow(\"Valid string model\")\r\n      << (QStringList() << \"+string\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \"$17\\r\\n__nice_test_data!\\r\\n\"\r\n                        << \"+OK\\r\\n\")\r\n      << stringRow << Qt::UserRole + 1;\r\n\r\n  QVariantMap listRow;\r\n  listRow[\"rowNumber\"] = 0;\r\n  listRow[\"value\"] = \"test\";\r\n  QTest::newRow(\"Valid list model\")\r\n      << (QStringList() << \"+list\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \":2\\r\\n\"\r\n                        << \"*2\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\"\r\n                        << \"*1\\r\\n$3\\r\\nfoo\\r\\n\"\r\n                        << \"+OK\\r\\n\")\r\n      << listRow << Qt::UserRole + 2;\r\n\r\n  QVariantMap setRow;\r\n  setRow[\"rowNumber\"] = 0;\r\n  setRow[\"value\"] = \"test\";\r\n  QTest::newRow(\"Valid set model\")\r\n      << (QStringList() << \"+set\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \":2\\r\\n\"\r\n                        << \"*2\\r\\n$1\\r\\n0\\r\\n*2\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\"\r\n                        << \":1\\r\\n\"\r\n                        << \":1\\r\\n\")\r\n      << setRow << Qt::UserRole + 2;\r\n\r\n  QVariantMap zsetRow;\r\n  zsetRow[\"rowNumber\"] = 0;\r\n  zsetRow[\"value\"] = \"test\";\r\n  zsetRow[\"score\"] = 1.1;\r\n  QTest::newRow(\"Valid zset model\")\r\n      << (QStringList()\r\n          << \"+zset\\r\\n\"\r\n          << \":-1\\r\\n\"\r\n          << \":2\\r\\n\"\r\n          << \"*4\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$3\\r\\nbar\\r\\n$1\\r\\n1\\r\\n\"\r\n          << \":1\\r\\n\"\r\n          << \":1\\r\\n\")\r\n      << zsetRow << Qt::UserRole + 2;\r\n\r\n  QVariantMap hashRow;\r\n  hashRow[\"rowNumber\"] = 0;\r\n  hashRow[\"key\"] = \"test\";\r\n  hashRow[\"value\"] = \"test\";\r\n  QTest::newRow(\"Valid hash model\")\r\n      << (QStringList() << \"+hash\\r\\n\"\r\n                        << \":-1\\r\\n\"\r\n                        << \":2\\r\\n\"\r\n                        << \"*2\\r\\n$1\\r\\n0\\r\\n*4\\r\\n$3\\r\\nfoo\\r\\n$1\\r\\n1\\r\\n$\"\r\n                           \"3\\r\\nbar\\r\\n$1\\r\\n1\\r\\n\"\r\n                        << \":1\\r\\n\"\r\n                        << \":1\\r\\n\")\r\n      << hashRow << Qt::UserRole + 3;\r\n}\r\n\r\nQSharedPointer<ValueEditor::Model> TestKeyModels::getKeyModel(\r\n    QSharedPointer<RedisClient::Connection> connection) {\r\n  QSharedPointer<ValueEditor::Model> actualResult;\r\n  KeyFactory factory;\r\n  factory.loadKey(connection, \"testKey\", -1,\r\n                  [&actualResult](QSharedPointer<ValueEditor::Model> model,\r\n                                  const QString&) { actualResult = model; });\r\n\r\n  wait(100);\r\n\r\n  return actualResult;\r\n}\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_keymodels.h",
    "content": "#pragma once\r\n\r\n#include \"basetestcase.h\"\r\n#include \"models/key-models/keyfactory.h\"\r\n\r\nclass TestKeyModels : public BaseTestCase\r\n{\r\n    Q_OBJECT        \r\nprivate slots:\r\n    void testKeyFactory();\r\n    void testKeyFactory_data();\r\n\r\n    void testKeyFactoryAddKey();\r\n    void testKeyFactoryAddKey_data();\r\n\r\n    void testValueLoading();\r\n    void testValueLoading_data();\r\n\r\n    void testKeyModelModifyRows();\r\n    void testKeyModelModifyRows_data();\r\n\r\nprivate:\r\n    QSharedPointer<ValueEditor::Model> getKeyModel(QSharedPointer<RedisClient::Connection> connection);\r\n};\r\n\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_treeoperations.cpp",
    "content": "#include \"test_treeoperations.h\"\r\n#include <qredisclient/connection.h>\r\n#include <fakeit.hpp>\r\n\r\n#include \"app/events.h\"\r\n#include \"connections-tree/items/databaseitem.h\"\r\n#include \"connections-tree/model.h\"\r\n#include \"models/connectionconf.h\"\r\n#include \"models/treeoperations.h\"\r\n\r\nusing namespace fakeit;\r\nusing namespace ConnectionsTree;\r\n\r\nvoid TestTreeOperations::testCreation() {\r\n  // given\r\n  auto events = QSharedPointer<Events>(new Events());\r\n  auto config = getDummyConfig();\r\n\r\n  // when\r\n  TreeOperations operations(config, events);\r\n\r\n  // then\r\n  // all ok\r\n  Q_UNUSED(operations);\r\n}\r\n\r\nvoid TestTreeOperations::testGetDatabases() {\r\n  // given\r\n  auto events = QSharedPointer<Events>(new Events());\r\n  QString infoResp = getBulkStringReply(\r\n      \"# CPU\\n\"\r\n      \"used_cpu_sys:17.89\\n\"\r\n      \"used_cpu_user:24.70\\n\"\r\n      \"used_cpu_sys_children:0.06\\n\"\r\n      \"used_cpu_user_children:0.33\\n\\n\"\r\n      \"# Keyspace\\n\"\r\n      \"db0:keys=3495,expires=0,avg_ttl=0\\n\"\r\n      \"db9:keys=1,expires=0,avg_ttl=0\\n\");\r\n\r\n  QStringList expectedResponses{infoResp,  infoResp,  \"+OK\\r\\n\",\r\n                                \"+OK\\r\\n\", \"+OK\\r\\n\", \"-ERROR\\r\\n\"};\r\n  auto connection = getFakeConnection();\r\n  connection->setFakeResponses(expectedResponses);\r\n  connection->setClone(connection);\r\n\r\n  // Fake callback\r\n  RedisClient::DatabaseList result;\r\n  Mock<TreeItem> fake;\r\n  TreeItem& owner = fake.get();\r\n  auto fakeOwner = QSharedPointer<TreeItem>(&owner, fakeDeleter<TreeItem>);\r\n\r\n  auto callback = QSharedPointer<Operations::GetDatabasesCallback>(\r\n      new Operations::GetDatabasesCallback(\r\n          fakeOwner,\r\n          [&result](Operations::DbMapping r, const QString&) { result = r; }));\r\n\r\n  // when\r\n  qDebug() << \"testGetDatabases - start execution\";\r\n  TreeOperations operations(getDummyConfig(), events);\r\n  operations.setConnection(connection);\r\n\r\n  operations.getDatabases(callback);\r\n\r\n  // then\r\n  wait(100);\r\n  connection.clear();\r\n  QCOMPARE(result.size(), 13);\r\n}\r\n\r\nvoid TestTreeOperations::testLoadNamespaceItems() {\r\n  // given\r\n  QFETCH(uint, runCommandCalled);\r\n  QFETCH(uint, retrieveCollectionCalled);\r\n  QFETCH(QList<QVariant>, expectedScanResponses);\r\n  QFETCH(QStringList, expectedResponses);\r\n\r\n  // Setup dummy connection with on/off lua loading\r\n  auto connection = getFakeConnection(expectedScanResponses, expectedResponses);\r\n  ServerConfig conf;\r\n  connection->setConnectionConfig(conf);\r\n\r\n  auto events = QSharedPointer<Events>(new Events());\r\n  QSharedPointer<TreeOperations> operations(\r\n      new TreeOperations(getDummyConfig(), events));\r\n  operations->setConnection(connection);\r\n\r\n  // Fake callback\r\n  RedisClient::Connection::RawKeysList result;\r\n  Mock<TreeItem> fake;\r\n  TreeItem& owner = fake.get();\r\n  auto fakeOwner = QSharedPointer<TreeItem>(&owner, fakeDeleter<TreeItem>);\r\n\r\n  auto callback = QSharedPointer<Operations::LoadNamespaceItemsCallback>(\r\n      new Operations::LoadNamespaceItemsCallback(\r\n          fakeOwner,\r\n          [&result](const RedisClient::Connection::RawKeysList& r,\r\n                  const QString&) { result = r; }));\r\n\r\n  // when\r\n  operations->loadNamespaceItems(\r\n              0, QString(\"*\"), callback);\r\n\r\n  // then - part 1\r\n  wait(5);\r\n  QCOMPARE(result.size(), 2);\r\n  QCOMPARE(connection->runCommandCalled, runCommandCalled);\r\n  QCOMPARE(connection->retrieveCollectionCalled, retrieveCollectionCalled);\r\n}\r\n\r\nvoid TestTreeOperations::testLoadNamespaceItems_data() {\r\n  QTest::addColumn<uint>(\"runCommandCalled\");\r\n  QTest::addColumn<uint>(\"retrieveCollectionCalled\");\r\n  QTest::addColumn<QList<QVariant> >(\"expectedScanResponses\");\r\n  QTest::addColumn<QStringList>(\"expectedResponses\");\r\n  QTest::newRow(\"SCAN execution\")\r\n      << 1u << 1u\r\n      << (QList<QVariant>()\r\n          << QVariant(QVariantList() << QString(\"test\") << QString(\"test2\")))\r\n      << (QStringList() << \"+OK\\r\\n\");\r\n}\r\n\r\nvoid TestTreeOperations::testFlushDb() {\r\n  // given\r\n  auto events = QSharedPointer<Events>(new Events());\r\n  auto connection = getFakeConnection(QList<QVariant>() << QVariant(),\r\n                                      QStringList() << \"+OK\\r\\n\");\r\n\r\n  // Mock callback\r\n  bool callbackCalledWithError = false;\r\n  Mock<TreeItem> fake;\r\n  TreeItem& owner = fake.get();\r\n  auto fakeOwner = QSharedPointer<TreeItem>(&owner, fakeDeleter<TreeItem>);\r\n\r\n  auto callback = QSharedPointer<Operations::FlushDbCallback>(\r\n      new Operations::FlushDbCallback(\r\n          fakeOwner, [&callbackCalledWithError](const QString& e) {\r\n            callbackCalledWithError = !e.isEmpty();\r\n          }));\r\n\r\n  // when\r\n  TreeOperations operations(getDummyConfig(), events);\r\n  operations.setConnection(connection);\r\n  operations.flushDb(0, callback);\r\n\r\n  // then - part 1\r\n  wait(5);\r\n  QCOMPARE(callbackCalledWithError, false);\r\n  QCOMPARE(connection->runCommandCalled, 1u);\r\n  QCOMPARE(connection->executedCommands[0].getPartAsString(0),\r\n           QString(\"FLUSHDB\"));\r\n}\r\n\r\nvoid TestTreeOperations::testFlushDbCommandError() {\r\n  // given\r\n  auto events = QSharedPointer<Events>(new Events());\r\n  auto connection = getFakeConnection();\r\n  connection->returnErrorOnCmdRun = true;\r\n\r\n  // Fake callback\r\n  bool callbackCalledWithError = false;\r\n  Mock<TreeItem> fake;\r\n  TreeItem& owner = fake.get();\r\n  auto fakeOwner = QSharedPointer<TreeItem>(&owner, fakeDeleter<TreeItem>);\r\n\r\n  auto callback = QSharedPointer<Operations::FlushDbCallback>(\r\n      new Operations::FlushDbCallback(\r\n          fakeOwner, [&callbackCalledWithError](const QString& e) {\r\n            callbackCalledWithError = !e.isEmpty();\r\n          }));\r\n\r\n  // when\r\n  TreeOperations operations(getDummyConfig(), events);\r\n  operations.setConnection(connection);\r\n  operations.flushDb(0, callback);\r\n\r\n  // then - part 1\r\n  wait(5);\r\n  QCOMPARE(callbackCalledWithError, true);\r\n}\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/app/test_treeoperations.h",
    "content": "#pragma once\r\n\r\n#include \"respbasetestcase.h\"\r\n\r\nclass TestTreeOperations : public RESPBaseTestCase\r\n{\r\n    Q_OBJECT    \r\n\r\nprivate slots:\r\n    void testCreation();\r\n    \r\n    void testGetDatabases();\r\n    \r\n    void testLoadNamespaceItems();\r\n    void testLoadNamespaceItems_data();\r\n\r\n    void testFlushDb();\r\n    void testFlushDbCommandError();\r\n};\r\n\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/connections-tree-tests.pri",
    "content": "\r\nCONNECTIONS_TREE_SRC_DIR = $$PWD/../../../../src/modules/connections-tree/\r\n\r\nHEADERS  += \\\r\n    $$PWD/mocks.h \\\r\n    $$files($$PWD/test_*.h) \\\r\n    $$files($$CONNECTIONS_TREE_SRC_DIR/items/*.h) \\\r\n    $$CONNECTIONS_TREE_SRC_DIR/operations.h \\\r\n    $$CONNECTIONS_TREE_SRC_DIR/utils.h \\\r\n    $$CONNECTIONS_TREE_SRC_DIR/keysrendering.h \\\r\n    $$CONNECTIONS_TREE_SRC_DIR/model.h \\\r\n\r\nSOURCES += \\\r\n    $$PWD/mocks.cpp \\\r\n    $$files($$PWD/test_*.cpp) \\\r\n    $$files($$CONNECTIONS_TREE_SRC_DIR/items/*.cpp) \\\r\n    $$CONNECTIONS_TREE_SRC_DIR/utils.cpp \\\r\n    $$CONNECTIONS_TREE_SRC_DIR/keysrendering.cpp \\\r\n    $$CONNECTIONS_TREE_SRC_DIR/model.cpp \\\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/mocks.cpp",
    "content": "#include \"mocks.h\"\n\n#include <qredisclient/connection.h>\n\n#include <QFuture>\n\nMock<ConnectionsTree::Operations> getOperations() {\n  Mock<ConnectionsTree::Operations> operations;\n  When(Method(operations, getNamespaceSeparator)).AlwaysReturn(\":\");\n  When(Method(operations, defaultFilter)).AlwaysReturn(\"*\");\n  When(Method(operations, mode)).AlwaysReturn(\"default\");\n  When(Method(operations, disconnect)).Return();\n  When(Method(operations, notifyDbWasUnloaded)).Return();\n  return operations;\n}\n\nMock<ConnectionsTree::Operations> getOperationsWithGetDatabases(\n    RedisClient::DatabaseList db, const QString &err) {\n  auto op = getOperations();\n\n  When(Method(op, getDatabases))\n      .AlwaysDo(\n          [db, err](\n              QSharedPointer<ConnectionsTree::Operations::GetDatabasesCallback>\n                  cb) -> QFuture<void> {\n            cb->rawCallback()(db, err);\n            return QFuture<void>();\n          });\n\n  return op;\n}\n\nMock<ConnectionsTree::Operations> getOperationsWithDbAndKeys(\n    RedisClient::DatabaseList db, const QString &err, QList<QByteArray> keys) {\n  auto op = getOperationsWithGetDatabases(db, err);\n\n  When(Method(op, loadNamespaceItems))\n      .Do([keys, err](\n              uint, const QString &,\n              QSharedPointer<\n                  ConnectionsTree::Operations::LoadNamespaceItemsCallback>\n                  cb) -> void { cb->rawCallback()(keys, err); });\n\n  return op;\n}\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/mocks.h",
    "content": "#pragma once\n#include <fakeit.hpp>\n\n#include \"connections-tree/operations.h\"\n\nusing namespace fakeit;\n\nMock<ConnectionsTree::Operations> getOperations();\n\nMock<ConnectionsTree::Operations> getOperationsWithGetDatabases(\n    RedisClient::DatabaseList db, const QString& err);\n\nMock<ConnectionsTree::Operations> getOperationsWithDbAndKeys(\n    RedisClient::DatabaseList db, const QString& err, QList<QByteArray> keys);\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/test_databaseitem.cpp",
    "content": "#include \"test_databaseitem.h\"\r\n\r\n#include <QMenu>\r\n#include <QSignalSpy>\r\n#include <QTest>\r\n#include <QtCore>\r\n\r\n#include <respbasetestcase.h>\r\n#include \"connections-tree/items/databaseitem.h\"\r\n#include \"connections-tree/items/serveritem.h\"\r\n#include \"connections-tree/model.h\"\r\n#include \"mocks.h\"\r\n\r\nusing namespace ConnectionsTree;\r\n\r\nTestDatabaseItem::TestDatabaseItem(QObject* parent) : QObject(parent) {}\r\n\r\nvoid TestDatabaseItem::testLoadKeys() {\r\n  // given\r\n\r\n  QList<QByteArray> keys;\r\n  for (int i = 1; i < 100000; i++) {\r\n    keys.append(QString(\"test-%1-key\").arg(i).toUtf8());\r\n  }\r\n  keys.append(\"test-2-key\");\r\n  keys.append(\"test-2-key:subkey\");\r\n  keys.append(\"test-2-key:namespace:subkey2\");\r\n\r\n  auto operations = getOperationsWithDbAndKeys({{0, 55}}, QString(), keys);\r\n\r\n  Operations& mock = operations.get();\r\n  auto ptr = QSharedPointer<Operations>(&mock, fakeDeleter<Operations>);\r\n\r\n  Model dummyModel;\r\n  QSharedPointer<ServerItem> parentItem(new ServerItem(ptr, dummyModel));\r\n  parentItem->setWeakPointer(parentItem.toWeakRef());\r\n\r\n  // when\r\n  parentItem->handleEvent(\"click\");\r\n  QTest::qWait(150);\r\n  qDebug() << parentItem->childCount();\r\n  QSharedPointer<TreeItem> item = parentItem->child(0);\r\n\r\n  item->handleEvent(\"click\");\r\n\r\n  QTest::qWait(150);\r\n\r\n  // then\r\n  // TODO: check mock calls\r\n  // TODO: verify \"load more\" item is last\r\n  // TODO: verify namespaces are rendered correctly\r\n  QCOMPARE(item->childCount(), (unsigned int)1002);\r\n  QCOMPARE(item->getDisplayName(), QString(\"db0  (55)\"));\r\n  QCOMPARE(item->getAllChilds().isEmpty(), false);\r\n  QCOMPARE(item->isEnabled(), true);\r\n  QCOMPARE(item->isLocked(), false);\r\n}\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/test_databaseitem.h",
    "content": "#pragma once\r\n#include <QObject>\r\n\r\nclass TestDatabaseItem : public QObject {\r\n  Q_OBJECT\r\n public:\r\n  explicit TestDatabaseItem(QObject *parent = 0);\r\n\r\n private slots:\r\n  void testLoadKeys();  \r\n};\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/test_model.cpp",
    "content": "#include \"test_model.h\"\n#include \"connections-tree/model.h\"\n\n#include <QAbstractItemModelTester>\n\nvoid TestModel::testLoadImplementation() {\n  // Given\n  ConnectionsTree::Model m;\n\n  // When\n  auto test =\n      QScopedPointer<QAbstractItemModelTester>(new QAbstractItemModelTester(\n          (QAbstractItemModel *)&m,\n          QAbstractItemModelTester::FailureReportingMode::Fatal, this));\n\n  // Then\n  // No assertions\n  Q_UNUSED(test);\n}\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/test_model.h",
    "content": "#pragma once\n#include <QObject>\n\nclass TestModel : public QObject\n{\n    Q_OBJECT\nprivate slots:\n    void testLoadImplementation();\n};\n\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/test_serveritem.cpp",
    "content": "#include \"test_serveritem.h\"\r\n\r\n#include <QMenu>\r\n#include <QSignalSpy>\r\n#include <QTest>\r\n#include <QtCore>\r\n\r\n#include \"respbasetestcase.h\"\r\n#include \"connections-tree/items/serveritem.h\"\r\n#include \"connections-tree/model.h\"\r\n#include \"mocks.h\"\r\n\r\nusing namespace ConnectionsTree;\r\nusing namespace fakeit;\r\n\r\nTestServerItem::TestServerItem(QObject* parent) : QObject(parent) {}\r\n\r\nvoid TestServerItem::testLoad() {\r\n  // given\r\n  QMap<int, int> databases = {{0, 55}};\r\n  Mock<Operations> operations =\r\n      getOperationsWithGetDatabases(databases, QString());\r\n  Operations& mock = operations.get();\r\n  auto ptr = QSharedPointer<Operations>(&mock, fakeDeleter<Operations>);\r\n\r\n  QFETCH(QString, action);\r\n\r\n  Model dummyModel;\r\n  ServerItem item{ptr, dummyModel};\r\n\r\n  // when\r\n  item.handleEvent(action);\r\n\r\n  QTest::qWait(50);\r\n\r\n  // then\r\n  QCOMPARE(item.childCount(), static_cast<uint>(1));\r\n  QCOMPARE(item.child(0)->getDisplayName(), QString(\"db0  (55)\"));\r\n  QCOMPARE(item.isLocked(), false);\r\n  QCOMPARE(item.isDatabaseListLoaded(), true);\r\n}\r\n\r\nvoid TestServerItem::testLoad_data() {\r\n  QTest::addColumn<QString>(\"action\");\r\n  QTest::newRow(\"load\") << \"click\";\r\n  QTest::newRow(\"reload\") << \"reload\";\r\n}\r\n\r\nvoid TestServerItem::testBasicMethods() {\r\n  // given\r\n  Mock<Operations> operations;\r\n  When(Method(operations, connectionName)).Return(\"test\");\r\n  Operations& mock = operations.get();\r\n\r\n  auto ptr = QSharedPointer<Operations>(&mock, fakeDeleter<Operations>);\r\n\r\n  Model dummyModel;\r\n\r\n  // when\r\n  ServerItem item(ptr, dummyModel);\r\n\r\n  // then\r\n  QCOMPARE(item.getDisplayName(), QString(\"test\"));\r\n  QCOMPARE(item.parent() == nullptr, true);\r\n  QCOMPARE(item.isEnabled(), true);\r\n  QCOMPARE(item.isLocked(), false);\r\n  QCOMPARE(item.child(0).isNull(), true);\r\n  QCOMPARE(item.getAllChilds().isEmpty(), true);\r\n  QCOMPARE(item.row(), 0);\r\n}\r\n\r\nvoid TestServerItem::testLoad_invalid() {\r\n  // given\r\n  Mock<Operations> operations =\r\n      getOperationsWithGetDatabases({}, QString(\"fake connection error\"));\r\n  Operations& mock = operations.get();\r\n  auto ptr = QSharedPointer<Operations>(&mock, fakeDeleter<Operations>);\r\n\r\n  Model dummyModel;\r\n  ServerItem item{ptr, dummyModel};\r\n\r\n  // when\r\n  item.handleEvent(\"click\");\r\n  QTest::qWait(50);\r\n\r\n  // then\r\n  // TODO: check mock calls\r\n  QCOMPARE(item.childCount(), static_cast<uint>(0));\r\n  QCOMPARE(item.isLocked(), false);\r\n  QCOMPARE(item.isDatabaseListLoaded(), false);\r\n}\r\n\r\nvoid TestServerItem::testUnload() {\r\n  // given\r\n  QMap<int, int> databases = {{0, 55}};\r\n  Mock<Operations> operations =\r\n      getOperationsWithGetDatabases(databases, QString());\r\n  Operations& mock = operations.get();\r\n  auto ptr = QSharedPointer<Operations>(&mock, fakeDeleter<Operations>);\r\n\r\n  Model dummyModel;\r\n  ServerItem item{ptr, dummyModel};\r\n\r\n  // when\r\n  item.handleEvent(\"click\");\r\n  QTest::qWait(50);\r\n  item.handleEvent(\"unload\");\r\n\r\n  // then\r\n  // TODO: check mock calls\r\n  QCOMPARE(item.childCount(), static_cast<uint>(0));\r\n  QCOMPARE(item.isLocked(), false);\r\n  QCOMPARE(item.isDatabaseListLoaded(), false);\r\n}\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/connections-tree/test_serveritem.h",
    "content": "#pragma once\r\n#include <QObject>\r\n\r\nclass TestServerItem : public QObject\r\n{\r\n    Q_OBJECT\r\npublic:\r\n    explicit TestServerItem(QObject *parent = 0);\r\n\r\nprivate slots:\r\n    void testLoad();\r\n    void testLoad_data();\r\n\r\n    void testLoad_invalid();\r\n\r\n    void testUnload();\r\n\r\n    void testBasicMethods();    \r\n};\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/console/console-tests.pri",
    "content": "CONSOLE_SRC_DIR = $$PWD/../../../../src/modules/console/\r\n\r\nHEADERS  += \\\r\n    $$files($$PWD/*.h) \\\r\n    $$CONSOLE_SRC_DIR/consolemodel.h \\   \r\n\r\n\r\nSOURCES += \\\r\n    $$files($$PWD/*.cpp) \\\r\n    $$CONSOLE_SRC_DIR/consolemodel.cpp \\\r\n"
  },
  {
    "path": "tests/unit_tests/testcases/console/test_consolemodel.cpp",
    "content": "#include <QtTest/QtTest>\n#include <QSignalSpy>\n#include \"console/consolemodel.h\"\n#include \"test_consolemodel.h\"\n\n\nvoid TestConsoleOperations::init_invalid()\n{\n    //given\n    QSharedPointer<RedisClient::Connection> invalidConnection = getFakeConnection(\n        QList<QVariant>(), QStringList(), 2.6, true\n    ).dynamicCast<RedisClient::Connection>();\n    Console::Model testModel(invalidConnection, 0, QList<QByteArray>());\n    QSignalSpy spy(&testModel, SIGNAL(addOutput(const QString&, QString)));\n    \n    //when\n    testModel.init();\n        \n    //then\n    QCOMPARE(spy.count(), 1);\n    QList<QVariant> arguments = spy.takeFirst();\n    QVERIFY(arguments.at(0).toString() == \"Invalid Connection. Check connection settings.\");\n}\n"
  },
  {
    "path": "tests/unit_tests/testcases/console/test_consolemodel.h",
    "content": "#pragma once\n#include \"basetestcase.h\"\n\nclass TestConsoleOperations : public BaseTestCase\n{\n\tQ_OBJECT\n\nprivate slots:\n\tvoid init_invalid();\n};\n"
  },
  {
    "path": "tests/unit_tests/testcases/value-editor/value-editor-tests.pri",
    "content": "\nVALUEEDITOR_SRC_DIR = $$PWD/../../../../src/modules/value-editor/\n\nHEADERS  += \\        \n    $$files($$VALUEEDITOR_SRC_DIR/*.h) \\\n\nSOURCES += \\    \n    $$files($$VALUEEDITOR_SRC_DIR/*.cpp) \\\n"
  },
  {
    "path": "tests/unit_tests/unit_tests.pro",
    "content": "QT       += core gui network concurrent widgets quick quickwidgets testlib\r\n\r\nTARGET = tests\r\nTEMPLATE = app\r\n\r\nCONFIG += debug c++17\r\nCONFIG-=app_bundle \r\n\r\nPROJECT_ROOT = $$PWD/../..//\r\nSRC_DIR = $$PROJECT_ROOT/src//\r\n\r\nHEADERS += \\\r\n    $$PROJECT_ROOT/3rdparty/qredisclient/tests/unit_tests/basetestcase.h \\    \r\n    $$files($$PROJECT_ROOT/3rdparty/qredisclient/tests/unit_tests/mocks/*.h) \\\r\n    $$files($$PROJECT_ROOT/src/modules/common/*.h) \\\r\n    $$files($$PWD/*.h) \\\r\n\r\nSOURCES += \\\r\n    $$PROJECT_ROOT/3rdparty/qredisclient/tests/unit_tests/basetestcase.cpp \\\r\n    $$files($$PROJECT_ROOT/3rdparty/qredisclient/tests/unit_tests/mocks/*.cpp) \\\r\n    $$files($$PROJECT_ROOT/src/modules/common/*.cpp) \\\r\n    $$PWD/main.cpp \\\r\n\r\nINCLUDEPATH += $$SRC_DIR/modules/ \\\r\n    $$SRC_DIR/ \\\r\n    $$PWD/ \\\r\n    $$PROJECT_ROOT/3rdparty/qredisclient/tests/unit_tests/ \\\r\n    $$PROJECT_ROOT/3rdparty/fakeit/single_header/qtest/\r\n\r\nDEFINES += INTEGRATION_TESTS\r\n\r\n#TEST CASES\r\ninclude($$PWD/testcases/app/app-tests.pri)\r\ninclude($$PWD/testcases/connections-tree/connections-tree-tests.pri)\r\ninclude($$PWD/testcases/console/console-tests.pri)\r\ninclude($$PWD/testcases/value-editor/value-editor-tests.pri)\r\n#############\r\ninclude($$PROJECT_ROOT/3rdparty/3rdparty.pri)\r\n\r\nrelease: DESTDIR = $$PROJECT_ROOT/bin/tests\r\ndebug:   DESTDIR = $$PROJECT_ROOT/bin/tests\r\n\r\nUI_DIR = $$DESTDIR/ui\r\nOBJECTS_DIR = $$DESTDIR/obj\r\nMOC_DIR = $$DESTDIR/obj\r\nRCC_DIR = $$DESTDIR/obj\r\n"
  }
]