pax_global_header00006660000000000000000000000064136225063360014520gustar00rootroot0000000000000052 comment=a0b66c61095bebb8e7772811b02687c552478de5 RcloneBrowser-1.8.0/000077500000000000000000000000001362250633600143145ustar00rootroot00000000000000RcloneBrowser-1.8.0/.appveyor.yml000066400000000000000000000006361362250633600167670ustar00rootroot00000000000000version: '#{build}' image: 'Visual Studio 2019' branches: only: - master - kptsky_testing skip_tags: true clone_depth: 1 build_script: - mkdir build - cd build - cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_PREFIX_PATH=C:\Qt\5.13\msvc2017_64 .. - cmake --build . --config Release artifacts: - path: build/build/Release/RcloneBrowser.exe test: off deploy: off RcloneBrowser-1.8.0/.gitignore000066400000000000000000000002501362250633600163010ustar00rootroot00000000000000.DS_Store build* *.user* scripts/*.zip scripts/*.png obj-*-linux-gnu debian/files debian/debhelper-build-stamp debian/rclone-browser* /release /bs0.cmd /src/*.autosave RcloneBrowser-1.8.0/.travis.yml000066400000000000000000000007631362250633600164330ustar00rootroot00000000000000git: depth: 1 matrix: include: - os: linux language: cpp sudo: false addons: apt: packages: - qttools5-dev script: - mkdir build && cd build - cmake .. - cmake --build . - os: osx language: cpp install: - brew update - brew install qt5 script: - mkdir build && cd build - cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake .. - cmake --build . RcloneBrowser-1.8.0/CHANGELOG.md000066400000000000000000000207461362250633600161360ustar00rootroot00000000000000# Change Log ## [1.8.0][1.8.0] - 2020-02-17 - NEW: http(s) proxy configuration for rclone - NEW: remotes icons size option selector - NEW: directories tree display for remotes - NEW: rclone extra default options for all operations (e.g. --fast-list) - NEW: added "Public Link" button to remote view - FIXED: option to show hidden files and folders was not always working as expected - FIXED: for sftp server default to home user directory (as normal sftp would do) - FIXED: an issue when on Windows local remote only allowed to browse drive C: - FIXED: problem using rclone and rclone.conf when path contained spaces - FIXED: bandwidth box on jobs tab is too small for fast connections - bunch of usual small tweaks and fixes ## [1.7.0][1.7.0] - 2019-11-27 - NEW: built all releases with the latest Qt 5.13.2 - NEW: changed Linux releases format to AppImage only - NEW: changed macOS release format to dmg image file - NEW: added installer for Windows releases - implemented using [Inno Setup](https://github.com/jrsoftware/issrc) - NEW: added Linux i386 release - NEW: changed macOS release compilation options to make it work on all macOS versions starting with 10.9 - NEW: added portable mode for macOS and Linux - NEW: on Linux multiple terminals are tried for rclone config ($TERMINAL then gnome-terminal followed by xfce4-terminal, xterm, x-terminal-emulator and konsole) - NEW: enabled Qt HighDpiScaling - should help people with high DPI monitors - NEW: added dark mode - configurable via preferences or system setting (newer macOS) - thank you @noaione for initial PR - changed preferences window - added tabs to create more space for new options - fixed Windows portable mode - fixed mount/unmount on FreeBSD - disabled mount on OpenBSD and NetBSD (as not supported by rclone) - updated build and install for Linux - now all files will be installed in /usr/local root - fixed possible crashes when old rclone is used (with different version information output) - fixed an issue with long file names leading sometimes to inaccurate transfer progress bar display - added additional info to file progress bar tooltip - individual file stats - changed program icon - bunch of usual small tweaks and fixes ## [1.6.0][1.6.0] - 2019-10-27 - fixed Windows mount/unmount (requires rclone v1.50+) - Rclone Browser checks now for used rclone version (mount is disabled in Windows if rclone 1.37 (by DinCahill) - Add a Public Link option to the right-click menu (by DinCahill) - Add preference: Show hidden files and folders (by DinCahill) - Add Mega icon (by DinCahill) - Refresh when Shared is toggled (by DinCahill) - Disable Upload button for Shared (by DinCahill) - Support for shared Google Drive files. Enable the checkbox when you open a remote, and all rclone commands will be passed --drive-shared-with-me (by DinCahill) - Set cache mode for mounts (by DinCahill) - Fixed missing leading / in path (required for some SFTP servers) (by DinCahill) ## [1.2][1.2] - 2017-03-11 - Calculate size of folders, issue #4 - Copy transfer command to clipboard, issue #20 - Support custom .rclone.conf location, #21 - Export list of files, issue #27 - Bugfix for folder refresh not working after rename, issue #30 - Remember empty text fields in transfer dialog, issue #32 - Error message when too old rclone version is selected - Support portable mode, issue #28 - Create .deb packages, issue #26 ## [1.1][1.1] - 2017-01-31 - Added `--transfer` option in UI, issue #1 - Supports encrypted `.rclone.conf` configuration file, issue #2 - Fixed crash when canceling active stream - Added ETA tooltip for transfer progress bars - Allow to specify extra arguments for rclone, issue #7 - Fix for browsing Hubic remotes, issue #10 - Support high-dpi mode for macOS ## [1.0.0][1.0.0] - 2017-01-29 - Allows to browse and modify any rclone remote, including encrypted ones - Uses same configuration file as rclone, no extra configuration required - Simultaneously navigate multiple repositories in separate tabs - Lists files hierarchically with file name, size and modify date - All rclone commands are executed asynchronously, no freezing GUI - File hierarchy is lazily cached in memory, for faster traversal of folders - Allows to upload, download, create new folders, rename or delete files and folders - Can process multiple upload or download jobs in background - Drag & drop support for dragging files from local file explorer for uploading - Streaming media files for playback in player like mpv or similar - Mount and unmount folders on macOS and GNU/Linux - Optionally minimizes to tray, with notifications when upload/download finishes [1.8.0]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.8.0 [1.7.0]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.7.0 [1.6.0]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.6.0 [1.5.3]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.5.3 [1.5.2]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.5.2 [1.5.1]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.5.1 [1.5]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.5 [1.4.1]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.4.1 [1.4]: https://github.com/kapitainsky/RcloneBrowser/releases/tag/1.4 [1.2]: https://github.com/mmozeiko/RcloneBrowser/releases/tag/1.2 [1.1]: https://github.com/mmozeiko/RcloneBrowser/releases/tag/1.1 [1.0.0]: https://github.com/mmozeiko/RcloneBrowser/releases/tag/1.0.0 RcloneBrowser-1.8.0/CMakeLists.txt000066400000000000000000000024131362250633600170540ustar00rootroot00000000000000project(rclone-browser) cmake_minimum_required(VERSION 2.8) if(WIN32) # link automatically to qtmain.lib on Windows cmake_policy(SET CMP0020 NEW) endif() find_package(Qt5Widgets REQUIRED) if(WIN32) find_package(Qt5WinExtras REQUIRED) elseif(APPLE) find_package(Qt5MacExtras REQUIRED) find_library(COCOA_LIB Cocoa REQUIRED) endif() if(WIN32) set_property(GLOBAL PROPERTY USE_FOLDERS OFF) add_definitions("-D_UNICODE -DUNICODE -D_SCL_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_DEPRECATE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} /GF /Gy /GS- /GR- /GL") set(CMAKE_EXE_LINKER_FLAGS "/INCREMENTAL:NO") set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /OPT:ICF /OPT:REF") macro(use_pch HEADER SOURCE FILES) foreach(FILE ${FILES}) set_source_files_properties(${FILE} PROPERTIES COMPILE_FLAGS "/Yu${HEADER} /FI${HEADER}") endforeach() set_source_files_properties(${SOURCE} PROPERTIES COMPILE_FLAGS "/Yc${HEADER}") endmacro(use_pch) else() macro(use_pch TARGET HEADER SOURCE) # TODO endmacro(use_pch) endif() file(READ "VERSION" RCLONE_BROWSER_VERSION) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${rclone-browser_BINARY_DIR}/build") add_subdirectory(src) RcloneBrowser-1.8.0/LICENSE000066400000000000000000000020621362250633600153210ustar00rootroot00000000000000MIT License Copyright (c) 2019 Dariusz Bogdanski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RcloneBrowser-1.8.0/README.md000066400000000000000000000532111362250633600155750ustar00rootroot00000000000000 [![Travis CI Build Status][img1]][1] [![AppVeyor Build Status][img2]][2] [![Downloads][img3]][3] [![Release][img4]][4] [![Codacy Badge](https://api.codacy.com/project/badge/Grade/e22f828fc0c94dcf9ddb3d38701d177f)](https://www.codacy.com/manual/kapitainsky/RcloneBrowser?utm_source=github.com&utm_medium=referral&utm_content=kapitainsky/RcloneBrowser&utm_campaign=Badge_Grade) [![License][img5]][5] [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/kapitainsky) Rclone browser ============== Simple cross platfrom GUI for [rclone](https://rclone.org/) command line tool. Supports macOS, GNU/Linux, BSD family and Windows. Table of contents ------------------- * [Features](https://github.com/kapitainsky/RcloneBrowser#features) * [Sample screenshots](https://github.com/kapitainsky/RcloneBrowser#sample-screenshots) * [How to get it](https://github.com/kapitainsky/RcloneBrowser#how-to-get-it) * [Why AppImage only for Linux](https://github.com/kapitainsky/RcloneBrowser#why-appimage-only-for-linux) * [Build instructions](https://github.com/kapitainsky/RcloneBrowser#build-instructions) * [Linux](https://github.com/kapitainsky/RcloneBrowser#linux) * [FreeBSD](https://github.com/kapitainsky/RcloneBrowser#freebsd) * [OpenBSD](https://github.com/kapitainsky/RcloneBrowser#openbsd) * [NetBSD](https://github.com/kapitainsky/RcloneBrowser#netbsd) * [macOS](https://github.com/kapitainsky/RcloneBrowser#macos) * [Windows](https://github.com/kapitainsky/RcloneBrowser#windows) * [Portable vs standard mode](https://github.com/kapitainsky/RcloneBrowser#portable-vs-standard-mode) * [History](https://github.com/kapitainsky/RcloneBrowser#history) * [Code signing certificates donations](https://github.com/kapitainsky/RcloneBrowser#code-signing-certificates-donations) Features -------- * Allows to browse and modify any rclone remote, including encrypted ones * Uses same configuration file as rclone, no extra configuration required * Supports custom location and encryption for rclone.conf configuration file * Simultaneously navigate multiple repositories in separate tabs * Lists files hierarchically with file name, size and modify date * All rclone commands are executed asynchronously, no freezing GUI * File hierarchy is lazily cached in memory, for faster traversal of folders * Allows to upload, download, create new folders, rename or delete files and folders * Allows to calculate size of folder, export list of files and copy rclone command to clipboard * Can process multiple upload or download jobs in background * Drag & drop support for dragging files from local file explorer for uploading * Streaming media files for playback in player like [vlc][6] or similar * Mount and unmount folders on macOS, GNU/Linux and Windows (for Windows requires [winfsp](http://www.secfs.net/winfsp/) and for mac [fuse for macOS](https://osxfuse.github.io/)) * Optionally minimizes to tray, with notifications when upload/download finishes * Supports portable mode (create .ini file next to executable with same name), rclone and rclone.conf path now can be relative to executable * Supports drive-shared-with-me (Google Drive specific) * For remotes supporting public link sharing has an option (right-click menu) to fetch it * Supports tasks. Created jobs can be saved and run or edited later. * Configurable dark mode for all systems Sample screenshots ------------------- **macOS**

**Linux**

 

  **Windows**

 

  How to get it -------------- Get binaries for Windows, macOS and Linux on [releases][3]' page. Windows installers (64-bit and 32-bit) are compatible with all x86 based Windows OS starting with Windows 7. If for whatever reason somebody would prefer not to run installer all files can be extracted using [innoextract](https://constexpr.org/innoextract/). Mac version is compiled to run on all versions of macOS starting with 10.9. Situation with Linux is a bit fuzzier... Linux binary ([AppImage](https://appimage.org/)) for armhf architecture runs on any Raspberry Pi hardware using Raspbian based on Stretch or Buster. Linux binaries (AppImage) for x86_64 and i386 architectures should run on systems using distributions released in the last few years. x86_64 one is built on CentOS 7 (released in 2014) and i386 on Ubuntu 16.04 LTS (released in 2016). The whole idea with AppImage is to build it on the oldest still supported LTS distro – and it should work on all newer OS releases. AppImage contains an aplication and all the files the app needs to run. In other words, each AppImage has no dependencies other than what is included in the base operating system. In practical terms it means that for example for Ubuntu Rclone Browser AppImage works on all versions starting with 16.04 LTS and for Debian starting with Stretch. With other distributions YMMV but I test major ones like Suse or Fedora. This is Linux. 10000 different distributions… with changes and customizations often only their authors are aware of. I would be happy to hear what distribution it does not work for. To make life easier when using AppImages on Linux, you can use [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) which monitors your system for downloaded AppImages and provides several useful benefits including: * **AppImage desktop integration** - AppImageLauncher allows you to integrate AppImages you download into your application menu or launcher to make it easier for you to launch them. It also takes care of moving them into a central location, where you can find them later if you need access to them again. * **Update management** - AppImageLauncher provides a simple to use update mechanism. After desktop integration, the context menu of the AppImage's entry in the application launcher will have an entry for updating that launches a little helper tool that uses AppImageUpdate internally. Just click the entry and have the tool search and apply updates. * **Easy removal of AppImages from system** - Removing integrated AppImages is pretty simple, too. Similar to updating AppImages, you will find an entry in the context menu in the application launcher that triggers a removal tool. You will be asked to confirm the removal. If you choose to do so, the desktop integration is undone, and the file is removed from your system. For all released binaries file with hashes signed with my [PGP key](https://github.com/kapitainsky/RcloneBrowser/wiki/PGP-key) is provided. It allows to verify that provided binaries were created by myself (authenticity) and are unchanged (integrity). If you would like to have properly signed releases with code signing certificates please see note at the end of this section. ArchLinux users can install latest release from AUR repository: [rclone-browser][7]. Fedora package is now available from [Fedora packages](https://apps.fedoraproject.org/packages/rclone-browser) - simply run `sudo dnf install rclone-browser` FreeBSD has its version available from [freshports](https://www.freshports.org/net/rclone-browser) website. *Note: For Windows and macOS it would be much nicer (to avoid pop ups about unknown software origin) to properly sign released packages with code signing certificates however it does not come free even for open source software. I looked at it and it seems that to get keys for both systems for the next three years would cost about $500 (3x$99 for [Apple developer account](https://developer.apple.com/support/purchase-activation/) and $200 for cheapest Comodo [code signing certificate](https://comodosslstore.com/uk/code-signing). I am not prepared to budget it as I do this only as a hobby and I am entirely happy with this software as it is. If Rclone Browser users think that properly signed software would be beneficial for them they can [chip in](https://www.paypal.me/kapitainsky) some cash for it. If I raise required amount I will get keys. If not I will give money to some charity.* Why AppImage only for Linux ---------------------------- Starting with version 1.7.0 Linux binaries are only available in [AppImage](https://appimage.org/) format. Some explanation on this... Binaries for Linux desktop applications is a major f*ing pain in the ass... as Linus Torvalds said - [DebConf 14_ QA](https://www.youtube.com/watch?v=5PmHRSeA2c8) at 05:40: > I'm talking about actual application writers that want to make a package of their application for Linux. And I've seen this firsthand with the other project I've been involved with, which is my divelog application. > We make binaries for Windows and OS X. He is talking about [Subsurface](https://subsurface-divelog.org). His small side project. > We basically don't make binaries for Linux. Why? Because binaries for Linux desktop applications is a major f*ing pain in the ass. Right. You don't make binaries for Linux. You make binaries for Fedora 19, Fedora 20, maybe there's even like RHEL 5 from ten years ago, you make binaries for debian stable. > Or actually you don't make binaries for debian stable because debian stable has libraries that are so old that anything that was built in the last century doesn't work. But you might make binaries for debian... whatever the codename is for unstable. And even that is a major pain because (...) debian has those rules that you are supposed to use shared libraries. Right. > > And if you don't use shared libraries, getting your package in, like, is just painful. > But using shared libraries is not an option when the libraries are experimental and the libraries are used by two people and one of them is crazy, so every other day some ABI breaks. > So you actually want to just compile one binary and have it work. Preferably forever. And preferably across all Linux distributions. > And I actually think distributions have done a horribly, horribly bad job. > > One of the things that I do on the kernel - and I have to fight this every single release and I think it's sad - we have one rule in the kernel, one rule: we don't break userspace. (...) People break userspace, I get really, really angry. (...) > And then all the distributions come in and they screw it all up. Because they break binary compatibility left and right. > They update glibc and everything breaks. (...) > So that's my rant. And that's what I really fundamentally think needs to change for Linux to work on the desktop because you can't have applications writers to do fifteen billion different versions. And I totally agree with above. I want to provide binary which works across as many Linux distributions as possible and I dont have time to fight with all mess with different dependencies etc. There are other similar ditribution formats e.g. flatpak but I had to choose one and I decided that AppImage is my best choice. I am not saying that AppImage is the best one but it nicely fits my objectives. You can see comparison of different solutions [here](https://github.com/AppImage/AppImageKit/wiki/Similar-projects#comparison). If for whatever reason you are not happy or your system is not covered with provided binaries you can easily build Rclone Browser for yourself. Especially on Unix-like systems it is very easy. Please see below step by step instructions for major operating systems. I have tested all of them and you can have your own Linux distribution Rclone Browser running in no time - it takes 8 min on Raspberry Pi 3B+, on modern desktop it can be less than a minute. Build instructions ------------------ ### Linux 1. Install dependencies for your particular distribution: * **Debian/Ubuntu and derivatives**: `sudo apt update && sudo apt -y install git g++ cmake make qtdeclarative5-dev` * **Suse/OpenSuse**: `sudo zypper ref && sudo zypper --non-interactive install git cmake make gcc-c++ libQt5Core-devel libQt5Widgets-devel libQt5Network-devel` * **RHEL/CentOS**: `sudo yum -y install git gcc-c++ cmake make qt5-qtdeclarative` * **Fedora**: `sudo dnf -y install git g++ cmake make qt5-qtdeclarative-devel` * **Arch/Manjaro**: `sudo pacman -Sy --noconfirm --needed git gcc cmake make qt5-declarative` 2. Clone source code from this repo `git clone https://github.com/kapitainsky/RcloneBrowser.git` 3. Go to source folder `cd RcloneBrowser` 4. Create new build folder - `mkdir build && cd build` 5. Run `cmake ..` from build folder to create makefile 6. Run `make` from build folder to create binary 7. Install `sudo make install` ### FreeBSD 1. Install dependencies `sudo pkg install git cmake qt5-buildtools qt5-declarative qt5-qmake` 2. Clone source code from this repo `git clone https://github.com/kapitainsky/RcloneBrowser.git` 3. Go to source folder `cd RcloneBrowser` 4. Create new build folder - `mkdir build && cd build` 5. Run `cmake ..` from build folder to create makefile 6. Run `make` from build folder to create binary 7. Install `sudo make install` *Note: For rclone remotes mount to work please see this forum [thread](https://forum.rclone.org/t/failed-to-mount-fuse-fs-freebsd/7723/9). For me it was enough to run `sudo sysctl vfs.usermount=1`* ### OpenBSD 1. Install dependencies `sudo pkg_add git cmake qt5` 2. Clone source code from this repo `git clone https://github.com/kapitainsky/RcloneBrowser.git` 3. Go to source folder `cd RcloneBrowser` 4. Create new build folder - `mkdir build && cd build` 5. Run `cmake .. -DCMAKE_PREFIX_PATH:PATH=/usr/local/lib/qt5/cmake` from build folder to create makefile 6. Run `make` from build folder to create binary 7. Install `sudo make install` *Note: rclone for openBSD does not support `mount` hence this feature is disabled in Rclone Browser. cgofuse guys did not manage to implement it: [#18][billziss-gh_cgofuse_i18]* ### NetBSD 1. Install dependencies `sudo pkgin install git cmake qt5-qtdeclarative` 2. Clone source code from this repo `git clone https://github.com/kapitainsky/RcloneBrowser.git` 3. Go to source folder `cd RcloneBrowser` 4. Create new build folder - `mkdir build && cd build` 5. Run `cmake .. -DCMAKE_PREFIX_PATH:PATH=/usr/pkg/qt5` from build folder to create makefile 6. Run `make` from build folder to create binary 7. Install `sudo make install` *Note: rclone for NetBSD does not support `mount` hence this feature is disabled in Rclone Browser. cgofuse guys did not manage to implement it: [#18][billziss-gh_cgofuse_i18]* ### macOS 1. If you don't have [Homebrew](https://brew.sh/) yet install it `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` 2. You might be asked to install xcode command line tools - do it. This is actuall macOS SDK, headers, and build tools. You don't need full xcode IDE. 3. Install dependencies `brew install git cmake rclone qt5` 4. Clone source code from this repo `git clone https://github.com/kapitainsky/RcloneBrowser.git` 5. Go to source folder `cd RcloneBrowser` 6. Create new build folder - `mkdir build && cd build` 7. Run `cmake .. -DCMAKE_PREFIX_PATH:PATH=/usr/local/opt/qt` from build folder to create makefile 8. Run `make` from build folder to create binary 9. Go to yet another newly created build folder `cd build`. Your binary should be here 10. Package your binary with Qt libraries to create self contained application `/usr/local/opt/qt/bin/macdeployqt rclone-browser.app -executable="rclone-browser.app/Contents/MacOS/rclone-browser" -qmldir=../src/`. Without this step binary won't work without Qt installed ### Windows 1. Get [Visual Studio 2019][8] - you need "Desktop development with C++" module only 2. Install [CMake][9] 3. Install latest Qt v5 (64-bit) from [Qt website][10]. You only need "Qt 5.13.2 Prebuilt Components for MSVC 2017 64-bit" (MSVC 2017 64-bit). Later steps assume you install it in c:\Qt 4. Get rclone-browser source code. You either need to install git and clone it or download zip file from [releases][3] 5. Go to source folder `cd RcloneBrowser` 6. From cmd create new build folder - `mkdir build` and then `cd build` 7. run `cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_PREFIX_PATH=c:\Qt\5.13.2\msvc2017_64 .. && cmake --build . --config Release` 8. run `c:\Qt\5.13.2\msvc2017_64\bin\windeployqt.exe --no-translations --no-angle --no-compiler-runtime --no-svg ".\build\Release\RcloneBrowser.exe"` 9. build\Release folder contains now RcloneBrowser.exe binary and all other files required to run it 10. If your system does not have required MSVC runtime you can install one from Microsoft [website](https://support.microsoft.com/en-gb/help/2977003/the-latest-supported-visual-c-downloads). Portable vs standard mode ----------------------- In standard operations mode all configurations files are stored in the following locations: * macOS: * preferences: ~/Library/Preferences/com.rclone-browser.rclone-browser.plist * tasks file: ~/Library/Application Support/rclone-browser/rclone-browser/tasks.bin * lock file: in $TMPDIR assigned by OS * Linux/BSD: * preferences: ~/.config/rclone-browser/rclone-browser.conf * tasks file: ~/.local/share/rclone-browser/rclone-browser/tasks.bin * lock file: in $TMPDIR or /tmp if $TMPDIR is not defined * Windows: * preferences: in registry Computer\HKEY_CURRENT_USER\Software\rclone-browser\rclone-browser * tasks file: %HOMEPATH%\AppData\Local\rclone-browser\rclone-browser\tasks.bin * lock file: %HOMEPATH%\AppData\Local\Temp\ Starting with version 1.7.0 of Rclone Browser portable mode is supported on all operating systems. To enable it you have to create .ini file (for Windows and macOS) next to executable with same name - e.g. if application name is `RcloneBrowser.exe` or `RcloneBrowser.app` create `RcloneBrowser.ini`. For Linux create a directory (not a file) with the same name as the AppImage plus the ".config" extension in the same directory as the AppImage file - e.g. if application name is `rclone-browser.AppImage` create folder `rclone-browser.AppImage.config` next to it. This is solution supported by [AppImage specification](https://docs.appimage.org/user-guide/portable-mode.html). In portable mode all configuration files will be stored in the same folder as application (in .config folder on Linux) and rclone and rclone.conf path can be relative to executable - so if in preferences in `rclone location` you put `rclone.exe` browser with look for it in folder where application resides. It means that you can put all required stuff including rclone binary itself and its config on e.g. memory stick and everything will be stored there. History -------- I have been using rclone-browser for long time and being annoyed by small not working bits and pieces I decided for DYI approach and this is how this repo was created. Original mmozeiko’s [repository](https://github.com/mmozeiko/RcloneBrowser) was abandoned and in the meantime rclone changed few things breaking rclone-browser functionality. I looked around but could not find anything fully working. Some github users made progress in fixing and adding stuff so I built upon it. I used DinCahill's [fork](https://github.com/DinCahill/RcloneBrowser) as a base for my version. I fixed whatever I found not working and added various tweaks enhancing functionality. I recompiled and repackaged everything using latest Qt (5.13.1) and latest platforms' compilers. This on its own fixed some issues and added new features like support for dark mode in macOS. Then followed with more fixes and more features. Rclone Browser was great again:) and is getting better. Code signing certificates donations --------------------------- If you would like to donate towards code signing keys please feel free to [do it](https://www.paypal.me/kapitainsky). If I don't raise required $500 I will give all money to some charity. Please see my note regarding it at the end of [How to get it](https://github.com/kapitainsky/RcloneBrowser#how-to-get-it) section. I will keep all updated with amount raised. Raised so far: 6.4 USD (1.3% of the required target) [1]: https://travis-ci.org/kapitainsky/RcloneBrowser [2]: https://ci.appveyor.com/project/kapitainsky/RcloneBrowser [3]: https://github.com/kapitainsky/RcloneBrowser/releases [4]: https://github.com/kapitainsky/RcloneBrowser/releases/latest [5]: https://github.com/kapitainsky/RcloneBrowser/blob/master/LICENSE [6]: https://www.videolan.org [7]: https://aur.archlinux.org/packages/rclone-browser [8]: https://docs.microsoft.com/en-us/visualstudio/releases/2019/release-notes [9]: http://www.cmake.org/ [10]: https://www.qt.io/download-open-source/ [img1]: https://api.travis-ci.org/kapitainsky/RcloneBrowser.svg?branch=master [img2]: https://ci.appveyor.com/api/projects/status/cclx7jc48t4u4x9u?svg=true [img3]: https://img.shields.io/github/downloads/kapitainsky/RcloneBrowser/total.svg?maxAge=3600 [img4]: https://img.shields.io/github/release/kapitainsky/RcloneBrowser.svg?maxAge=3600 [img5]: https://img.shields.io/github/license/kapitainsky/RcloneBrowser.svg?maxAge=3600 [billziss-gh_cgofuse_i18]: https://github.com/billziss-gh/cgofuse/issues/18 RcloneBrowser-1.8.0/VERSION000066400000000000000000000000051362250633600153570ustar00rootroot000000000000001.8.0RcloneBrowser-1.8.0/_config.yml000066400000000000000000000000341362250633600164400ustar00rootroot00000000000000theme: jekyll-theme-midnightRcloneBrowser-1.8.0/assets/000077500000000000000000000000001362250633600156165ustar00rootroot00000000000000RcloneBrowser-1.8.0/assets/appdmg.json000066400000000000000000000007771362250633600177740ustar00rootroot00000000000000{ "title": "Rclone Browser install", "icon": "../src/icon.icns", "icon-size": 128, "background": "background.png", "window": { "position": { "x": 500, "y": 300 }, "size": { "width": 576, "height": 384 } }, "contents": [ { "x": 436, "y": 215, "type": "link", "path": "/Applications" }, { "x": 140, "y": 215, "type": "file", "path": "../release/Rclone Browser.app" } ] } RcloneBrowser-1.8.0/assets/background.png000066400000000000000000000673321362250633600204560ustar00rootroot00000000000000PNG  IHDR@EzTXtRaw profile type exifxݙ[v ߹,$H\/9AmYx&'OVJWI#RZ䫌2Esy|x~y?o/3³~&hyt^>N(~e_y/}"ɯ0^ }y7OE)ƩOr&IX^WV!C&,9;yX~὎ ^׉mP~O>(/_wkw@{S߶⃋SsX[_y7];luaLo*餙nyK<缳;e2Tb5q3'j7U[:Mww-e _Ӏֹd Q!-(x@˼Sڐ+ SZr (H6 :4I5&6qgJ`~:8b ;碜0!F]*/ppAm 9kk=0.jꇟxqSP[qrC1zf]eW8W H@ecM/B`1!}[~3+!O6 8!!dkYj' !+V!m啟hնNV{=qݬ(t-4 n6$D^wIq9 ǑcĒÖ_V{vރPo+uFoz="$>3q8Ҙ^2xpvAU* 6X?>;_u7c'EhR&ø1F;*mJz4r%mnd'̓oU!Fq6qh*Av' R n34> `~!a4:QUТi݃970`C L,)*n)sxe.k{8v>Âe+A:8:`SٚEAEΉӂ&ЖOV"aQט}?+^S= p(&=t@_T? Ff՚cu 2<6Bpy2\. EgKd{UINCwr"~Lljxu]:QlHD!FX1vvw$oOQ<}@cy;G#GP 1΁+yaNR|q2;QK@z A3dRrX!@DKZ-c nT)3[OH &UX 4ִgu3)[^mxxaZځFd !,![`rTDⰚEGJ@w ; &2ŀp8qvC#UE͌".^Tj~ ,oǪGx/.c$ .Kp2Ý(%(WCroI-NSbd)6i׳XON˒ش qa ^N*1 L\"1>Rgɕ> ~*v"˽Yڤ}sL-U#mm]17>y1w-L%EeopSvޠG;PXhmYnTT%&ZF"\P=n ((ah2*3N$q]TsRWb.PO3:.Ylt\(K8Q?L\p c)^mf1H =HuA"U4q:E.dMC<=ft5BYb\i2p޳vL ȋ-Ť)$;[1&w%GRxrd4$M_Yƅ!Kvfe&# h!DLB265jh~:'D nJ6ebfҸE?Cq¸A H[ߡhG"N'inP#V,>r23M{ r3lMK[@\$!˼`{P@!2@ބ\%?M2 FXT}#J!A [sOX ic*=^j$720F4Dm`!^,l:mqN31bG8|}T9 9}Fx1Ew5}jFguHICN5 7W&"'m,ycҹҜ }((KSY<cl]0Iِ)apCb$~(6Y4ϐ V3v,Y}{݉E"\֚G=T\p8A P6޻،2wD$VDaNnYgV+̔xu@!b$&L4JRLKUH\2B WYB7AȯE`C&k8;fŵGF[^tjxb uLvT9\dNϳd}P"iۚqt!l( . !]W0m1Es?i deb||+ss'S01A1+QUԐUQr7^e( "0Dy6 ݞg`#=ݑIB ՛&ƭ0wo*ɇAcx1BRe&р8`d tlJ1^ ~=Uyt iW?Cm[%џiMmsT3C^< y|sv`9/DžGqap`pd󗍟QZ,^. Llf;;y$ޙ0m]3NRhEϑqfxZ2IΤm:, _u0h}Nx0 B$`)X ւP`?8Vpp@ 0 ރQAZ>dYA A!P$%CiA+PTUB{zW8t@w~hz}ú)<vp0 /%p.o ~ !#tF\$"Y"eH ҈#]MDD>apƸc11.f f5fӂ9 caX cӰK2l{ۋľptKV6vpnk0</WOo d>OH$eSQ фF #ˉĽv5 qJ2#yI餵rR#<-L6$#Br||ODQXRX$AKyKRMDj6u3zQdV)QRjQJlT^\|TK KZJJʈ*MN5L5KuKjj~j<|Zj4fDcѸAu:[=]HzXeU'5tnJg3#3tg0ggl8ƌ355MZ~ZZ[Zjc-#j>rLܙ3̼XDЩչ3+֭=R筗WwJoH//?`22sa@nQC3uMHF.FFFFơ+M\L&;LL>ƙn0m5}fi65k0{`N52_b^c~gba%lh)f[9Y vZr%U3Ϛbʹαnۄجiy5xv쭳fuʹk{N.n]{K{}-6ssǑ񫓳ĩi9ٹڹE%eEWܜܲݎn~\{xzp_޹΁A0t0C4ћòM#G]639VLk.lZ Zm m=ǃw7f۾'Nj,>E:jtq3ig:u?ֹs_lE'.]:~r+-W6{sSw5km]9uƙ7/bߺ;v;}I};;f}}/U=yTMr'~S''eO?vb__,KW毎גco6zݜw##gPQO.>}~: KW߂=s$VANM 'P]4hœ}8P@t!X*{j7o:O"I[[ollulkZ=:OrLVbHoŎ<_/^+!0iTXtXML:com.adobe.xmp vC iCCPDisplayHPSY{/zHB EzJ Aņ ("\QpU`[oEE] 6T9;s9޹ TG,΄UeK"| @2 pbfxx@ejN x,DǗr0(,+dEFK|eu Z 9m9e1 Hʚ($ 1jgp8_mE<exrh^0++k8Q6O.NLQp< ! L_2eS9LQ$HGN`RM7?Y`s)q|s3LqП͎b/j%#R%,s$ye1 VDMq0vK3}X D/ X{ يق@9ExEm<ߴO_% W3viNbn6z +0>ap`pd󗍟QZ,^. Llf;;y$ޙ0m]3NRhEϑqfxZ2IΤm:, _u0h}Nx0 B$`)X ւP`?8Vpp@ 0 ރQAZ>dYA A!P$%CiA+PTUB{zW8t@w~hz}ú)<vp0 /%p.o ~ !#tF\$"Y"eH ҈#]MDD>apƸc11.f f5fӂ9 caX cӰK2l{ۋľptKV6vpnk0</WOo d>OH$eSQ фF #ˉĽv5 qJ2#yI餵rR#<-L6$#Br||ODQXRX$AKyKRMDj6u3zQdV)QRjQJlT^\|TK KZJJʈ*MN5L5KuKjj~j<|Zj4fDcѸAu:[=]HzXeU'5tnJg3#3tg0ggl8ƌ355MZ~ZZ[Zjc-#j>rLܙ3̼XDЩչ3+֭=R筗WwJoH//?`22sa@nQC3uMHF.FFFFơ+M\L&;LL>ƙn0m5}fi65k0{`N52_b^c~gba%lh)f[9Y vZr%U3Ϛbʹαnۄجiy5xv쭳fuʹk{N.n]{K{}-6ssǑ񫓳ĩi9ٹڹE%eEWܜܲݎn~\{xzp_޹΁A0t0C4ћòM#G]639VLk.lZ Zm m=ǃw7f۾'Nj,>E:jtq3ig:u?ֹs_lE'.]:~r+-W6{sSw5km]9uƙ7/bߺ;v;}I};;f}}/U=yTMr'~S''eO?vb__,KW毎גco6zݜw##gPQO.>}~: KW߂=s$VANM 'P]4hœ}8P@t!X*{j7o:O"I[[ollulkZ=:OrLVbHoŎ<_/^lbKGDp pHYs%%IR$tIME  Ɵ IDATxw|uϒ& PE4AM "" HO1"A8"I^?")#Rf~xɥζ؇̖~AQE@ @ @ @ @ @ @ @ @ @ @ @  wNk<|P$%%E$==]J(!Rtier,HNNdIMM4QEܤTRݝ.WJttsss///X+WN*T eʔɷ$/__~Eq]w]Jݺujժ6L&ٶm(dIiٲ,Y޽+ϟM6 t=nÆ2h iKSO#qsʭ[t?55Uyj-[PÇJTTҲE 3g*׮]s>tH~#""~nuʕ+e̛77=C(&())Iyx:7(R=_w"՞xB| 9u9iղ<_|6s +ˤIݻE.pɶmۤl4 @r|<-\|9ϞOP9tɗUeĨQ"bor]plް!6[7nߍeȑ2ztʱ߿_M&sfp92~L?^֮(Q.-^~Ynǻl-˖)SdNOٺm<|HUJ~Z(r$*JV^-3'gO.+WJ*UӧoxCzBۧd2ɷ~+>'#njO?`##G E `0H@F2O{Æew%6V۷.˖-y~2,Z@ԫ'7o,tҷo< ?Yoeg%…|(/_^f͚%mү_?16f j._ӅgzժrƍBsݻ'o˲jNVX|4`߿O W^yEvݛիeCBl޼Yܹ@<'xPȻݻ˞; |\ۻ\vO f͚eСܾ/u6Z>>vzp̚=PtNKK1G˶͛.˿^=۰Tv.:H.}Ξ""f+S@~)ӽ^GJ``c߿/˭ZIǎ͠ VZ/]$7m~IWHhи,ȓMΰlR WK:w&;w&M)l6ˉ'5k$r:=&}eV֤jԤ|GZ-;vBTU$F0(c"Ā tteٙ2eOfo-[Pl:/*=zP-rB33g0oJjjM}|lG9m"Dݻ DZ0Aԯ͛Eј~qFٴig_~ ~Z,Y",2[jԮ]PʴSmzL6m•+ɈRtiۡC9~ ?mz\_ȁ\~>z\r_YP'V[)III3U>CzXF>c~r1e5k䥗^*4x|K֯_oPGyDΝ+_8}Y.ǵk2t}y,u4I,nwY`r/Y";vt1.]ZkS8ܼjSpܭ1ӧˀ׵H9r4k̥%rר 2/ Q,uTE~a|A=~aaa2xBu~O:%֮յ|6wWp 6ŋɹ=|, Yʺhٲe#6<#G*йmq~Y3gr(,wwwOto駟ҥKyr}<@wFqyYWߠ5u7|ĈB3)%''4fҲeK?.eu믿O?st;>^"KLgPTúd$ILL,RB)YGbZZ$%%ɽ{M*W,Kk EQl6KZZ$&&JRR r,,E$FQ$55Uܤ|!}L&IHHd1 %^^^.ݷ$>;wdJ*g~-wlU+WfꫯZ|ѝW]UYbՅ to|7xC۷mz}7NOk k.lae˖IhhvZ_kWHKK 6Xd&RRlٛe K.ݻwJJlٲEWA///Y|nڦ7n`0X Mwyl߾]{Nl,4Ҿ}{E~{gˈ#zN $III~z]|2by,^ k:$Νx;11QM&3g̵m,Itȑ}?Qr…\ۘfK.N -0y/_կWOyωdR&N^kVIHH{۷+[ni~[ybbb[ZZZ(--M/UӺ8q®q]tIQE1ʐ!C4b ]wjs?Tum۪5ed2>_?汽{h]oooeL8smk֭[[v;wؼψ2׬Yݺu4߽{w*RSSۣԬY⯧uW*(JkY?crss&M6)IHHp2¯#5:wqJRz8`|}^Aܹ#Z?оsSL2EL&SÇC>3ZtTPA"""./rɺu4G7|S1c4;?u_޽dɒ=>>^^x?~O,XkKʕeժUvw裏do֡ H ,#GGHUoo<{tYb*ٳmҴi#vM`]>_"<( CX"..N:udicǎ8mΨ%JٳeŊc2dÉP;t FR"$C-T\&>>^Wcǎ~4<4ZBBiF k[(ݺu~95kL~a 5{쑾}"RRRggEz%cɣ>JΜ9k5jqUe!h/OOO/uo12R̙ E4h vrJY ,I&lvرc?aaa6c 9rfWڵ_WVkD'ݻWW-\||?~}mڴuƌrIg}6iiiҹsg>^^^RJRxyyY-m۶o9ݻ']t)E.)";wC_Y߯\)mڴQ}\rrK3'6kʕ+g&-4wVj۪Zji6O0A6mċ6mDFF:\;ps̑oeuΜ9pBST)͎3sfU[z90ӣG\_OEi^z믿ɓU;Q:uJW5/[3'qqq(/^>}hݵkWk+#z޽+.QB^k:^^k~yd9ν^y8t`0WUxnZfPC 8PbbbÇ(eĈߩS'|1ʤI5kȄ C:^ٰaܽ{WEEQΝ;f`N_5F6kLVСCreSSSmjSKUgk߾}׳^zrUYx4nXW.uԑ1cH\\\tU]uǎ2}t/_,_}ݥVZ2}t4iL6M&LY֊+dРAN9_>3)SLر2m48qBs{ŊcǎұcG9|iSҥK-.R\fMiժjؑ#G?XDҬY3/dټy^us}i@KU jժɀСCRbE!))IsU`UR~}oo8e5kʶm,60HaM8Q NUvuoo2 ^Q:w6mʳ󒘘~ ivM1c0xW”0ŋm*722jZÎ ϸ-]}v27nܻwϦM6,sϞ=_lzXjC>~7-Y$J``cbccuOڵkU~z_?jy>ϸ3O`EV['wQQdܸq:ew rm~Vg9ѵm^62B NSO=%l;s,uMhUPP|7=zԡOm (&22R"J*ҥK5GFFFv@nԨj^sk^zluM,5Yw -+6/Zcw^'+ ,(3K#GO#jcҠqT9sHppC_dz}l3>6}iآy2/tovryfv-HLyԩG>e%XcJ::ݻu59pfPњXM54̙3G3<#͛79Y 3xb2LYG ͥ^yYsqgG *U$5x߲eꪱ`/VMrˋԩS[ݺu͡q;>^Bz:w5Ҧ];]:c4HQ @zE:~\~Ǥ(DEE|9e۷tby)]ٳbT".tr'VaeXRlY饱ѣG%%%Ů?3ҥK>>>תU+iGeNjK& ͚={X=RާOUR_d+WnݺYP䯎Z:v(?}vIMM?^ /`KVduImgKE*UT^lٱo;-SbӉ(QB^mV׶:ʡ8ѻυg5:e԰a7W'Tg3f~cd|;k_nԯ__ 6ecAM>}T\Ysŋ5>bў={,n ;߿_\cbbTáVW-v}7J6mCjժ%͚5+Vda /`N:c̠Շ~Xl-}Zl){رEeA5Mǵkߒh[̼eGpׯ=;+*T\YDmy GM6lzVMB5 'NwJJ/B汨*K#6nhqN:io2dM)66V/)SFׯk2K.icɓ]Gd|f0d6ˌ!zVai訚9~T-[V>3F׶msig߄/_^zI備{j^Klw3{Yk-coS_Z 5)))}vz_RjǏ93l##`jO8g|i=ߺuN8zl_'OڮI&FTubyK,ִsyrssY|Co͚5Z2_]@""?2#G^k`0HPP!Yj+WϳʩS uhРAyZܿ/a| wq1[Nƍ{={9zGdԩR)`͛7;<("55UnݺU`~%kh^7oTL2'suėuf~kdmrR[[ngSV֭Fٱc]D4rHe=XPNM4*""Εƍ[]’:uHnݲ#%4$D<˔qq2a1y5Ϻ;'d[߷ktM/_.*Urxׯn:޾gy3oݱCBׯ_wh۷o,,"u:GjT;uꔼ[礱Dkp/~[,X`ÇKz$..baaa^jդQFk#f@ձcGK,)Z81$ǎJp ՟Lf̘!>3<3A4lҔ^2QK9q]e9roO_tIyq\֪YS̙;tY[jey{G?i6ӧ|~7r ښϸ쌙Ϟ=[(gp6ܹs*?jJRR]3A_sj{]7___ղ?zY||``ݻVXM43g*UX_a۷'ZA9""BIOOm۶nq<hf(2z˜,Çܿ/ӮfzpRDDƍ%͚IJ<5 СRC+AAbEnÐo7__`ڽ{ԫ[WƎi̜9$?o~s۽pݻw%""B!'Qb?eGFFJLLjղZF*UEۻw\~]u XԩSm}GqI||怙\9>ꈴC\E*V(QǏKۓcN!CŋmXD 2Kƒ٪zfKҡcGءTQ#vqqqcYAGꅳg_7t0112|pGXSOСC3?̦cX. ]uֹF˙3gdժUӆ rRӜ^nJQT)8qu\9+ݻWs'0Kf\:ג3g̙3mn ꫯ4lԨQwjZ4mT5S7nlSNiƍﭷR]᧟~8Ov'T}͛78:+({IDAT(ȥ>oE6ֻw\Ma;urJ/*uUKw2;v,_oN2E1ͺ˝7o2oݺebXHmSϹs2=۷o+7iSk;vyԫW|nmzoBIUyذav5_WrsXV]vg?4TYh^& @Z\8{VbN =&P=!FY[9ǜ<)8yog]v:Wu֕$_ƌ#;vWjnw-ի 8Psٳg;Ӿ=6l9ڵk /իKz4 inG}Ԧ02>c4{v^4ѣҺuk ?Cc^jq8_-8hBuٳgk.Srr.ǎZj25@yƍ6|/bw'輬pM*U Ĺ۽{myT(b4? eʒ%KhҥKJLLrJ]W)%66VU٣;Vrm>C>UT50c˴bϞ=i׮]4֩;UA;udk֭6lF NE6)d ءjժ|9zC#eTw1ۛ?0)ܺuKOrarHQss]z]v=%00P>f͚yRmu-[Tҥi&Ν;(dRN>7c7NY|vZ믿lƍ+EQ֮]x̙vuԎjլ\vM  EQey~\/*7o޴W_ Ї~hS۸q,ȏL]ݹsGiذBCCu]rKd2)Cu;1m 9"`D|MefUAsG^STLM GyD&L UwEKȎ;^3/_#φ.ڵsJyCѣG;4Ѡ3 5j|N/;""B (m_Ե]Ŋ}Vk߾_5rիXMf̘!'Nt16-N]TD 1bDeztnߊ+ŋNJvNn˭ZًeԨQRt"s-7h NX1cңGuN; WFnʕ%KM;H/Z$3g,p D(QB>#ե qQ۷Cy}Ĉ6M'6ֹ_*s7duNeرsN]vrmCPL,],Hrrݿڍri颲p>8P^(۶oל:0.]HBrl۵i!r%wL<١%򛻻=Z5mʧEJ*ɖ-[$<<˗%$$XҤIIJJ?vrJINNV]ݞ:hnӬY3~j0-\ܹSu9 ;&~͓c iٲܿ_ϟo8 6l(ԟ{Š(y3"W`˖-ɞ>}Z_wժU6#pbbezyӥuRZ5&3Lk׮|IS:}meݺu:u^!!Rn]Rk{5(Ԯ]%aիri+"R|y,AAA6ao߾]u_l=w/^kѣnh"iժTVibٳgeٲe2k,må]vRZ5Cܹs:u؋9%%E>|(fYE777qwwҥK'wHNN1b22Csɒ%G2eL(:RSS%99YFxzz癔$)))NKKEQDRT)̓PEQ$))IRSS]?OOOh@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ;F}J (fF?$&&ڮ\r2p@iڴxxxp8EQN-FQ]&ׯHdxzzJ֭eΜ9RvmN"!%%E/͓X͛KDD!@̵~/dǎ= AEX;oʕ+i@P\xQ"##6sI͚5%,,L<<<$ @l6KTT?~\FxOOOW^y `h2e޽joooՑ^FQe,VbtQ`GOz-AAA'RJIݺu3̄1͜t5@@1d6%66V+ԨQClW۷]v_`6m͛s;jb&ϢE,r4dpsswrrL&.c4eϞ=|SfM>YGf > bff1t@]~~~&!!!' ֭[Y ȯ/cNΞ2uT 9_z/m6d4e…2|7o.cƌ_~٥M5fY2gyX\dٰa\tIz- yZ_SٗqTTDEEe]re.V#::Z,֭1c,8]]듕db|q{n9rbX5kݛ|FPlsIHH <8Wi޼DFFJhhh?AAAzBBBd풒‹ #%%Eԩ&sŋW^ɗ9ifs~~~E.f17[Vڽ{K׮]ܹs1Bܹsj`~"qFcsʶGtttT%[o=h@t|kut^EQ,YNZZ̟?_3+:vg-6vXiѢͅ@JJ,ZH 8c6}% @F1[ɨqeбfRD $ xSk*L92DEEIJJ̟?_-.]~P|Ykbxsx5xDGGKdd݁O*WwPPe&Ç85e˖ɓ'4 @7&ѹg%66֦P5d 9 DDD&Q7op|qYh,u d0 Cr`EQRY㵎t..~<Ks,^^9_ZuYz^;jAׁ-YhsE[{[ZC}u-_ίֱ3U/u|t j[|]T* c =j @ PI @ a$  (@( @ P1WG9E=!a @ 7@(t yj4(L!  _BP` DPljϗeXB @"BmBė!u @@q A! !@LЀ h @gRC-bCP8&06+ !(nA@ E@@ @@A@B@ 5j (h(A4 @ @ 8@!@A(:@B@j  P)IP(7C3PÏ'b~B@;yh8 c,"|' uɹ/ E"Z!E:h A@B>ZFPOQCw&D@ ,@=⊉A@ T@1 @ @ Ks /f>~ $F4b @ @ @ @ @ @ LIENDB`RcloneBrowser-1.8.0/assets/background@2x.png000066400000000000000000001317361362250633600210300ustar00rootroot00000000000000PNG  IHDR\ zTXtRaw profile type exifxk$)s=8loO][]CrpWTBh-UF2zM(xÿ7~{{^ޛʵ>߃gd qHBo*L7uQ^E^*CcdD&ss-w|L֙OJb/0OɃ9+%YMyb3O;qX5}`+?yα-?Y=!bb@wIRTM8v3Y" s#9]|n>yVnC/$斕P@$Zց 5RkmUkΖ[iyjj֢Uv:{޺GCFjmc9tX'7ʪ-]}57eݶǞ& L۰yJz8󂵛oqϬ5k3YKɓ(N?mC$9#cRWYyr*'ǒg 3wӼw&\/27إl{LGx|}fh] gb%(džJnp}ZZV+5bm]`C ga86jmHoL-hk{2!se%c4a'k̓+.>Ql'zܽKç 6_A5~(HRXc-Ć:ӗUKi`Qpch&mwZu2g%lELț!SX N/5hwmvT- Dao}=b egy+:qQh QC|DoQkGk]&b)2ﹽ+mZT8Xe:Ub\˞.!wXC2E+nmoM"*ԊZ;VU%{ QgGO"ظu \cuGȢ~.B@maeNN%`$aI "M+A'i֋z1'OԻiNu8@ l(n$,܄P9F;,q/'n߄xcd( iC͙JQkwm# ieE6_sT*q@>jJ٘Rr"m# HyJ_?eK|_(96EX!hvysT2P׻29#s`K,Uy2HWi_`*f8TXvGE b!;,2J4VJ7\?w`W{0h*t> >7h8cl\@'c~UH!!F`+-ϡQ 5Ago 4M Q!pЋ#eA7h{;qe,FA,iPBEXmG&!A+5:y܄?˰ ; z:6goǪi{|N6*<tDBhY2Qyl [+ 8{‰oYvG}du,¼t>;,"qNÄ)}$'{L@T4o`!iTqz%!捞 6\{E Obb;^L9abp.)PAY6\bU$ '5H/oF9>@.P015u3@!@ qQwmI9-XsJK 1q+I*}3^`.ǚ:(͏D^ڶETfÊ p#ظ+hI|(.7e$2*T, hrQPƅ yhW1nLx|6݆[4@ dj}3ČuqjP4/pC Gc:_܄'{H':|6iOka"(C$~@eMd{hۆ[B3R@]N$P$"X $)۶a:ZMg'5=fA܆<9x@G1<Oc6n  J\sdM3AQ$66W K@#̒{eو`yF0[S]Tvb-Dm[fK4W4b }1yn/s#ϧktd R"6q +Xv9ƁwU|@o\feZG;,;Ķ|nY\:N~@Ou-tcZm9h` Dpyr2/m*%$Be F!2D»D}fŜMV&y#+)Sid` +6@5wdbJHyط 5%R%^JmD?Lð7UHO`T9lx"DyBeOWM&"dc |袻1.DA}8 yy77FQX88 C#U@OJV!8t(,`2ayG5p ގ2܆8TL7^5@aU[BIMtạ̊́YB(P UJ?A\.ij>lDIe#V1tAX > RzW.9hܗ%%1&ϧ/E`Ih-"[b7'F*e Sn垜޿Nqo ⽅cMU>5+nj\blT޾_SM /,T]r(; |_aHV6u a4؛{w~X=eGkJ@o74xMdu f6NXitHLӽJ{|wŏ' D?S xtPsei(ŏT?Jhr-67c~ZKͻEش 2Űy9%#)z{O|QUn'V w<l!NZL T0sB_R* k`;ual1B#a&ZAۜAHrxO%Vݯ|z[1s@gǍ2OKnos=Jh_g!> }S~5|5?gPR^J/b#b ts iCCPDisplayxPSY{/zHB EzJ Aņ ("\QpU`[oEE] 6T9;s9޹ TG,΄UeK"| @2 pbfxx@ejN x,DǗr0(,+dEFK|eu Z 9m9e1 Hʚ($ 1jgp8_mE<exrh^0++k8Q6O.NLQp< ! L_2eS9LQ$HGN`RM7?Y`s)q|s3LqП͎b/j%#R%,s$ye1 VDMq0vK3}X D/ X{ يق@9ExEm<ߴO_% W3viNbn6z +0>ap`pd󗍟QZ,^. Llf;;y$ޙ0m]3NRhEϑqfxZ2IΤm:, _u0h}Nx0 B$`)X ւP`?8Vpp@ 0 ރQAZ>dYA A!P$%CiA+PTUB{zW8t@w~hz}ú)<vp0 /%p.o ~ !#tF\$"Y"eH ҈#]MDD>apƸc11.f f5fӂ9 caX cӰK2l{ۋľptKV6vpnk0</WOo d>OH$eSQ фF #ˉĽv5 qJ2#yI餵rR#<-L6$#Br||ODQXRX$AKyKRMDj6u3zQdV)QRjQJlT^\|TK KZJJʈ*MN5L5KuKjj~j<|Zj4fDcѸAu:[=]HzXeU'5tnJg3#3tg0ggl8ƌ355MZ~ZZ[Zjc-#j>rLܙ3̼XDЩչ3+֭=R筗WwJoH//?`22sa@nQC3uMHF.FFFFơ+M\L&;LL>ƙn0m5}fi65k0{`N52_b^c~gba%lh)f[9Y vZr%U3Ϛbʹαnۄجiy5xv쭳fuʹk{N.n]{K{}-6ssǑ񫓳ĩi9ٹڹE%eEWܜܲݎn~\{xzp_޹΁A0t0C4ћòM#G]639VLk.lZ Zm m=ǃw7f۾'Nj,>E:jtq3ig:u?ֹs_lE'.]:~r+-W6{sSw5km]9uƙ7/bߺ;v;}I};;f}}/U=yTMr'~S''eO?vb__,KW毎גco6zݜw##gPQO.>}~: KW߂=s$VANM 'P]4hœ}8P@t!X*{j7o:O"I[[ollulkZ=:OrLVbHoŎ<_/^+!0iTXtXML:com.adobe.xmp 懣0 iCCPDisplayHPSY{/zHB EzJ Aņ ("\QpU`[oEE] 6T9;s9޹ TG,΄UeK"| @2 pbfxx@ejN x,DǗr0(,+dEFK|eu Z 9m9e1 Hʚ($ 1jgp8_mE<exrh^0++k8Q6O.NLQp< ! L_2eS9LQ$HGN`RM7?Y`s)q|s3LqП͎b/j%#R%,s$ye1 VDMq0vK3}X D/ X{ يق@9ExEm<ߴO_% W3viNbn6z +0>ap`pd󗍟QZ,^. Llf;;y$ޙ0m]3NRhEϑqfxZ2IΤm:, _u0h}Nx0 B$`)X ւP`?8Vpp@ 0 ރQAZ>dYA A!P$%CiA+PTUB{zW8t@w~hz}ú)<vp0 /%p.o ~ !#tF\$"Y"eH ҈#]MDD>apƸc11.f f5fӂ9 caX cӰK2l{ۋľptKV6vpnk0</WOo d>OH$eSQ фF #ˉĽv5 qJ2#yI餵rR#<-L6$#Br||ODQXRX$AKyKRMDj6u3zQdV)QRjQJlT^\|TK KZJJʈ*MN5L5KuKjj~j<|Zj4fDcѸAu:[=]HzXeU'5tnJg3#3tg0ggl8ƌ355MZ~ZZ[Zjc-#j>rLܙ3̼XDЩչ3+֭=R筗WwJoH//?`22sa@nQC3uMHF.FFFFơ+M\L&;LL>ƙn0m5}fi65k0{`N52_b^c~gba%lh)f[9Y vZr%U3Ϛbʹαnۄجiy5xv쭳fuʹk{N.n]{K{}-6ssǑ񫓳ĩi9ٹڹE%eEWܜܲݎn~\{xzp_޹΁A0t0C4ћòM#G]639VLk.lZ Zm m=ǃw7f۾'Nj,>E:jtq3ig:u?ֹs_lE'.]:~r+-W6{sSw5km]9uƙ7/bߺ;v;}I};;f}}/U=yTMr'~S''eO?vb__,KW毎גco6zݜw##gPQO.>}~: KW߂=s$VANM 'P]4hœ}8P@t!X*{j7o:O"I[[ollulkZ=:OrLVbHoŎ<_/^lbKGDp pHYs%%IR$tIME 'c$d IDATxy\N?W%l[hQbd 1l%N%%C&XFLeRh33ss/xc\:׹}y DDDDDDDD$[l""""""""ycH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9d """"""""cH""""""""9dtAvv6W^˗HJJ›ׯ5RSEA…QD *U JBҥQ\9)S&&&(W . ===6.JP ##/_˗//_~so߾EZZҠEʖ-2eʠlٲ(Wʕ+SSS)S +4Cvo#>DffVMOO(R-ŋzzz Dݻwqlݿ?lmjRyAE}jj022bGRRNN;oK'ZTAoWW4FssԮ]:u'%%!11Q~Z*-G<@vv(VZAvV/֏@u}TAۖ,YMDDD*$ BN=;wL}Z jש333T^UVWR(HLLĕ+WpiYBuhjo^{(VZC;D+nl,j֬oƒ_!((GWG GGGXYYRJZr ̄(e^G﹬, R^s>sFqtp? .+W֙Nж޽1O: &@:4K<=`eeʕ+@055W^EPPF>|"._ffơ}S_tJPpn݊sNi.?[׭=ziӦ(UσرQU'4ijj#"""՟Eiٵ+jWwww;v _%&&bphXL0~ ApyclDEEa޼yQ2ϿزڷG=sslڸ?hǐCvN"""""5`H۽?vZիcƍxKRRv܉,,ЯW/\ :Gۖ-ѣ{wZcr+W`̘1hX`V71!GFձtRYHbx=;*!)?z4W___#GCݑsr8ׯ~tS(ɓaۨüY`^&|||LAMNKDDDD$fMư?''HOOǛׯq-3|8vlߎ+V^sC\~-۽vҕ+2eD˼y4n,gĉXnVY:p²:_3LA о}{v^"""""|5#dee!##)))x ?{{AAHIN.PyaahXbF%JhU;wѣe٧fLAAXjlllm 0aa:DZ1klԨQCV>p _:u#I P!:\T^=:89!''o߾q[9NETTV\kű`̙8(~u)4M`֭߿?)o III/O$ܹmc:t 0uܵ %Kd&""""siG2e`cc'ٳ8{< vF}B>C`HLLdGրǏaС|n:aٲeHIIq ٩D+^8aF\r}3g6pV Ά?86;_ׯG~&}v8ٳ1|p͝cDDDDD"cH 0LLMs0q$0X222zj BJNsgjpqo1_m߳zesL-?1C . ]q?9 ڵkՖ:==+VSyܽu ڵ˗Q( DN 4].Gy,'%9&LׯىDUvܙg#44TdffbŊXĄlgdff1DP({1>s+* =~Q6#gΝ:˗#++'H (Y]{3ӧMûw$Cvv6֯_s[c044dcСCpۗ huut˗p sr,$"{%FqqK] 7oJ+BJJ ˸z۽-,0eT … rJ.\ ɀ> k }}|axX F&M *T@ѢE&##/_ǏquۇgHRmvёRdX`.!ynv*UBҥ 3ϋiz\3s&6lNN:~^VVV)m_vŊ$"""* d^z2r$nٿW@5TϾ߳G6bX 6Tap¨\2*W ;;; <<@hh(~(Qh2 a<׫u?A`ccCɒ%QdIԫWΘ:m^ݻc3f Μ9jի|_hҴ)/ l"""sa>/7nPw՚w'߹kעYfk``ڵkcРA%zU1bX? 1*cajEF={СC?y)UZ֭[qML:Um86˖/Gff,`x.Ͽ]OĒKrmڷDž0x\uJbŊm۶8t0~V66Jҽ;,X(QFF/[Sv-Zd9s`aae˖!,2..ji˭6ԩSwo¬YƋH 7|<=_t YYY>{,)1,ZIӦjNUH899!IxDD4O?t|8}4vn&~.]hѢڒ%ك7e3g@RR,7 i޽TfJJ ͝+iKѣ(]^JJ .X Y}MLMq!4kL {;vElyꕤ}9Z (~/86ӧOFDDDDaHfKF,dϋTznF@eu000@=iz֭;~7>h۶NCOAjjË( L~+}+VL~;qB:VRӦO׹` GH+;;\SJ(Sa``1cƠD~79~ ټ רYS[ny\strBx?$)wƜ9Z'/Xx$e/??jK4uHbGrr2ijuF(\p9I8e|W*SVV=*Innn:=}]ppt9 ߼y͚5]>t(ԩoIKKCZZ233 B}}}.\E [1@vv6 PX1ETddd 33 zzz000!-g-\zz:>]+S QH)<~+\0z璫Oߥ999(T+Ԍ@2}.RK^FF8 z;8Ύ'իWؿgΘ3fff:6FFF8QPDD,@ 8{E2exW'''qqqkffSSS/_M4%֭jժH"oLŋqu<{dmmM4AݺuV>ӧxܹWÇHJJ‹/ƨX" UUe ASܿwAdd$>}D,--ann5j|:JKKóg>]7bbbի< 7FQvmTT #鈉kpܸqO>EllW4hmۢI&U/. `HFBY+p9?d%c|[nIRn=d>[%nEEZ0j(W˦-[`|I +1o|=.\/mmѷo_nyi{%Ο?v999h߾=J,g޽{3g'ŕ+WpcԨQXn[DEE̙38z(,99ɟW7333j :uBƍQJI]vv6~7 ھB ~ gϞׯѥKI4?.YYYXJJ "##qI9rjqqqzHH?$֭lmmQN?ƍ8w /44;wΝ;o߾h޼^n={K. N:vBB_gΜɷ}???̞= .[23]a_֭r`jТ"--eZ^xVxxxα:VuY!+uٳԩS;"<\qiI@=33Sѣ{wӦukENNtuqm޽{/a5 SPiÇŋ+LMM0EVVZݻw[ff"$$Dacc#1c(%9hի撜ǏFR\xQ.۰a:JzტJ**۷%3˗/[nUXZZJ;w8~"%%E+L?R*22R_ n̘1ӱo>4mC  ggg899ٳ[366s΅%F%q'''cѢEE$ΖZ||<ƍ.]Kq\ >,իWm ҥKh۶ּЇlzzFF|}}akkU#X&L///^kK1p@d vA=:ϿZ+VTAٻOf@M_tEQX2O8L8͛cA"""Çkh[nysH-;;+VJDcbڴiZ|ٮ];A߷w.hd\vMqheI0]P_SGLݹs]vʕ+pss=?Nrr2fΜÇkuٳg#55UꖙsڗL0rrr7g9Rk"00P޼yqaժUZ}YYYZU'N`ӦM@:Ç>}];oQ7RV/AիW+dFFF$Ԧ!Eܹsaec#IQW`ܹHOOH:#99{KiCbx-̙uILLĐ!CDf۫@  v܉s"ҥK1dĉmܹؿիW8q"o2{CIL-ssl7G }Vz-['|;T%Jm5qI ~Ν;P(x'"%%E+~zIt~:u>׽{w \2m+vE%b'tRv 4>::&LeL<EUL,\P%;v w͛''' >|E|||˛p0}a)kaPvmz/+I< IieFWQP7L0666غs'IRQ~}B$Ç OOOeZq[~LK\\F Q]v~)888r,b,o>}:J,rY>|}*UBbŠP(ܺu GQ:8vAtM;w0~xn5k;Mbb"qQlذAp~/_dƍ @Zb8q3NE@?L я.S~ދ<ĨhQȶݔk׮a5?N;"mcjjudɒ(\0rrr<}T&M-[62Ξ=+YgusaʕXp{l&M6ml8p@Ý;wT:Ə޽+(h0g*UꋟKLLļyC-Pt\?caa :oVZv^"""0tPۯYS7Æ }(SH:ϙ Vs+TH- b֬Y%v~3f`(V/FnݺgϞD PD‡ɓعs`ϟ@Qp{ԪU KF"EѣG~:"""QFZϞ={tn/MLLĽ{pIҥKѺukmVm+TQF MwFpO>lE .Re۶m 4jFh~!4x{{8 bhhKKKZ ]vŸqϙc޽N`]o߾< *5kbѢEh޼9\]]] .D6m I8;; 5j@ҥQpadff"99>qefw 6z) dee!33iiixpm\xGHU k||$K#'uti)^rrrd^R6211м$}wQF?~>^핕$PT.}~ %$vZY"y7n$]m۶!Ğ={J,cbΜ9j Ri{OOO̜9S۷ѣGU*cɒ% 4}>|XpEhree8q?Wm65jTU N~ZԶbA  55 akkzvA͛mcڴi(\0o: 0e B/\O?aÆ*U*p2- s6dSTM*/ʲ>}Dj}B__nNNNXdd5J-V[M=== 4;w\ơC47LLLԲk׮\W) jW,=RGH%#Ο?6JJ*/k׮ő#G4NAܾ}[Pkwww&]]pN`HyjVjR(Xjܙ ?JsTtiT33cGP<~G!Jv xp#^@3v,"##t?ĉسwL;m~P3gqjg1c  >((777۫mBVph^[,Ƙ={6BBBmb ̞=[ׇ%LiFnݔJcǎ9\]]1|=r?ԳMjj*o.(QB{ U1w\_e 4GZYX1nJ}Edddhdθq~!|5 cccfTŋqUF7t`9wNeUtiT\Y\IRHf7'N^ITD _/yN8+Wb֬Y>}3.m[J\S1ٷ ݻw >66V#!CHk222a_Zh!z0w4L2s!vz9~?yRrtMj֬yEpVillRJ?MM&}XPBpss;w^*UJm{Nѝ0aWskhhA ޽{k۶}*3J.? A]eMHjkkkA?*Mrr26mڄ֭[ wW^ hggiMBWzjmm]H7b-6aG/NaҥXd~Kcܳs'.\(;i… écGD ]0tPW=tUV $)Ѐ:Fnݺit 6D*U)41JAjS:;;kt* }7oDϞ=ޮnݺԩSF|ݻwZO>F:uR Wru 00P6ׯ-Fsqqqعs'vߏ1UTAfЮ];XXXF(_ŋe˖>8))I102/(l>ްD5ZO?cG`Э(DDDu:>YYYعc2=== 6 W"#_}XRHL\rر#|}}6&&j}EKNmٲe1bJo+4'(tA̘1@e8;;X!#bccqm4k싟}>T\'NTi!00SPc ݻqư}֬Y#Y[Hٳgjߧj8L .ٳg.)VFBxxFYzu_WÇ߭[ 33Sƍ$;vlN@"xIA͚5%Y1QY͛7ݓ'OguںukRˀ>4i"hp+lRvV]v)Uۡ^z?{l>wuF([nݺСCַ)SUV(_>2P(ސ7lsJAJr2"AB p`U+WTZ!jժh,K 22RFP`A B1VJl4M6/%ĭ[-uiӦ:}nqN:oΝg>|ԲV? t C޽add$|իWOЪcbٴiڴikkk͛7* ym,[,o24h[q+* gVek׾$._?)׾}%){ʕ׮]ƵkE/V. ;;;l0=HiKvU*j‹.t%dXCCCi>wE2R)<<111~&11JҹC~0{lhQFVivbbWq/HNNFvvZeIZ i fΝuV'%ᅦst/;'fG?HRn_uMӱ|2IvswW 0p@ 1Ɋ$"帠0K/f|nUQʹ-H$r1k;_ot;88㍡!fΜ ///Xt&M«Wݻw\UTN;I̙3_ѳwoI9tUDH֭*:&G!DVdُ)sJ 4EZM.BG8i˹500ȹ533m/]^zԏ?yV O˯_IЫW/u322[{U 222d]JcHJ,k;pHEȲE }8޿v\-Z<<$)y\ϟSݹs# VZK*TƍyQlh4VUrH8ܪ<Bl~w*5-[nƲW~!!!y3RSS~8;/4h4 19sW:F4%++ R@VV-K/'M»w^7kkkXXZJR/vޭ3)11ƍÌ3D]M6-[٤$L0AUz`7v'R7mIpʳH͏$tʋ[cccmӦMM;qx@TKM K.a틡TyTR_ŽӿH iKO?5k֨}xc1y1xƦ)ѣG8`yz̚9A TT s.Zɇ0 CB$)-쾊ԫwoE:ڵkZQK-$v˗]llV ] SU&xXnt*1}~@cǎ嚴7""B{Z^t144-|}}q=lٲNNNؑς9Ut@Z@OOÆ C_77c <|@PoDTMdD[~OoXJ4-+7˗ǜs1~hIOINK8tVJ¡C?k@_O .j^եN:6{6/Z$>z? =zКPX0>~ZJ}4W՟LLLڵhio/ٔ:"u|?<| 4X233qiAVPA\ IDATĵJh>|oVcuOOOGQm۶%Sƍallt˖-G+;bcǎ\ѹsgٳG]ʕ+gϞJ䤑}QjԨm"''o߾ųgp=… _\L1 BG <zҙ:OcHm=j>oFm{w,? HLH@fp(8;vxȄ\>~n70e`Nzzzpww4Aʕ9rƃxO>ԩS_ɇJe͚=[_juM ev t˗5z AeJ**[M>|s ڶQFԡB 0`W^G(88X?[PHH:v===F (S ʔ)k׮HMMqMXvhۻw/<<<]bEA]vWSLӦM%9/^Ă :\|pTG?vիWkl{vv6Ξ=NN_ |yRVZX%D^fLCjl¹cGɃ?=bK9sqqt//^\֭[ѩ'O:G.k*7j9Bs[^=ѭ[7A۝>}x7-[III*US~ 8t(#/^gϞ]Kyrs YH]B3qb߼~=vuZh̜2}zFdDZ[#JU֬X 0$~F߳͛5î]{)ϟ6?[QQoƌOšB JhI(W6KbbJשSGlٲ*%2tF+,RaĭӒf~pVڵR*V(h9^~tNOgggԬYSgSSSt AAA8rU*/>WxqA?r;wNpr{"]*\0fΜ -ȡCNXdZu `̙t_111Xd ,FnW-[E W.[ qssǎ_% c˖-hfk ֬_SKƚ5k$MlO$E5PсߏMMMQzuٟ===tU/})0-U&Z]LLL %77oJm׸qc%d)vшUj[WWW*{Y;'N6_Rꄎ8ͅ* ira ~mEҴiSx_P建u˗/ׯcڵ1o,Q^/x(33:/7-[T봝Kvؾ};f̘%osV^۷o/\0aΞ9>0@nйK4;TReʔA"EHLLÇ#06ƒK=ə-^,ZWW߿_.^8tUѣGR-KB~SLQ UF:uNaРA'%%aҤI*"B}V۱cڴioӣGAS˕+)S(ԈEDDR*)MF`oo/h\Z5t;ɥ"22R큶\r|0#bHu Ǣss4lLQ eʔ `Ӱ"ʕ籰DannoLM?Ȥx1?zǏm,]zzz1cRÏoeb hբF>|\k ccԩWk@Z`bb"E ''> >!wnՒKvFWV7n^vc^_]pQԯ__}sU*F ܴ!F)8/EMoɘ5kU`v*Y$OYJccc1`i֬z:;;+=C/u-%T3###XYY )tJ`ٲeسgV}-ƏbƍBf+ kӺ۷o]]\r=;:[bܹ|1;;[շU{YerPۘ0&H?BQŸ$CM%"_Ay<:ZrVG lp^{^ŕk=Y:<}~}ow\K/~}??dY_[64q܄ ُ>[@-9jʔz?o>|x*vZ*{i彍rJl6]ti_/cog~l}٢=ӧO}Gyޟ#GfW^m|w}-v655}w3f/}̞=[+}dǍ׭Y`A^WSO=u7{ 7t98>lUVeI5vmΝ;wpN^ztuWK@oǝ3rM4)]yyf~%X>OŢ뮳#J/r?>~t_|1=X`A޷u 'ZSyь_~9:蠸;bÆ ۶?OqQGW_L<9QFСC8uQѧOn~ٙ}k%rʘ1cF\y1eʔxg1 ^yn}o.Sv8u?vZ#;[oŌ3b…nqqٳcʕ(:gUbw`Hիc̙/Yl=z3fΌox`fuV^SbV1mڴ8 L/]JTիW׾8_~Xpa92~_m'NkQjvѣ~lg/VZ ,c5=լYvN.'񍍍EWw:UG}to'tRp שt5U#jz]v%0aBL8o ?'O޸4wܘZ|E톆9rd޷3zh`t~>ۭK??c{_|yx9Xn]\]w5/wGxw 4׿<0n撝QT]ZZZ'pBk"?-6Z^f͚]vm5Жσ8<&׳+W,s=56ܜkpkYjOCCC[V[n>٧~:?1sef'N,p u彿q VׯΜ9vС /pc3ds=}dzsO/r!E9K>1cr|vi.(x1u.]4|83z.d̙pAuf[>wcflKKK_ɓug.;bSPzײ{'x;dO^'#sɾ{]oPP6;9s8 6Μ}w|xNo9 6dy)SW_}u^?~|ޏŘ1c-ʾ ٵk׶Ge[ZZ/Rn*H`0aB^((BK\_l_3};\mׯ_\ve1xn.U I'TKh^zŹ'bZ OC?fΜi͟7n\\pa~vtӂ b=#"bw رcgKs\I,m765r=ztAm)'|2/˻=1uԈKbرnŎ;{=zĆ boK/v[tMyǂ bkO?'tGǞ{Xn]\2.]v[Az(O{o׋*LJ;6.Zn򂴩?qĕW^nmIim??>n1Jюŵ?iz5wrѣGp 1t8/MM57~ru1i$W*?>y~dgP1≮+~;>8ꨣ<`n#xG .;+6l'xbiӒ<9|8묳 .}7pA~ꫯ.e#kjN.{㎋*w~PKO2 5nܸ:thn5b1uԢO:~_TG[?]җK,)1PHcǎᄏh@QFŏ99Ļ[mٳg|G;s}cW//>`So*ذa⪫~8xcv +؇΋'~:8apU.&LvpdEn!/#p2C;#"iw}㗿eY7nƘ3gi_0bĈXxq?sEm)=XL4)zQ1۵Wev+H)zĈqmŌ3*;vl<#qavm-}qUWN;EѣG8묳ok{q]wu; _|qxя~{w9N=Լn!sϢmcϞ=裏68㌒}Co!{^^aР8ߎ>TbҤI֗n͎N/첸 ~L4)y䑸ꪫbxv`/<~8묳Jz߇vX}qw!RAo#8"ۘ>}zݻ~6g[o'O{,⊲LlWEG]xa|;v|_nO-]|ϯuuuq衇Ƹq׿u\q7>fΜGuTѫW/]#g{c̙_2/^=iРAq5jT<w|E]v%N<ĸu9;>|<888ꨣG[o5n喢cԩq衇'?Ɋk&#Gw>}zEJ|Xu*ĹgϪzo7cҥ9߈#b~݆ W_wO~k/9:3fLNeo9h=xWg{,hnnm}q~{WUGœO>_3&S|r^zuڰaCO<K,|۷7v8bر1jԨmݪbӺubٲew_Om|1nܸw}cذaE֖+Vtkl @P7{/zX|_7cUo|A|QQWWzEc1`~W^E?˗/~;^*Z{/}Alذ!zu[olMǀc4hPz@|ᇱf͚x7_7|3VZ֭={ƶn=rРA;.N.{+V7|3/_+Vw}7֬Yׯ=z6lv!G}}}~Eu[n]^:|x뭷bʕjժXzul ԫWׯ_?vq4hP 80D?Ν/nw[h@@Dl>꧳EOg۶bŊv6#rqBFMe20`@s9%n69-[~9jT1СC }dcE9jL1O>}N&!CDCCCۻ~Xv5jRM40|@P sOL @BM*w ?$ӽ4@BN:ujs9@ !sYgŀ*'@ 1t/t @P#PtKKKdd2G($9Q?Ҏ֠lٲ2d0  @ {iwPZZZb޼yqGsss444DSSj'^1lٲusss|͛ r'@E-4mذq 9rO-hhh!Cjnnŋ_# TШk׮׷5ƍݻ(Q[N }34- : @wW~ZZZbdɒv.녀rK444M @Pa 1+յ~\ Z cTЉQ?=3ǥZM2*Ĩvs0؜eR~f͚e 0_A>wOfȐ!1-  ()Skܸq{3eHzQ?l6֮]ׯokꢾ<2- Z%@S---1Xd*D6ؿYW(Pk (Q?ۗ˖-)Qǩjڵ1Xlm%|P+ (p> =CmNS)1ϴ0jE:1Δ/~㹫N.L  ed9)_FPwؓb̦† gώzUM<;k֬YFtSCCC 2ľxJI9tu4qzFP- (Љ)_5nܸhll޽{1C"@'զ|U796; >bOמF 8Δ/~(1}9jT)'ݝe>S5յ5MOcc(I ||Y|JY2dH466vxE5kęg7|sɶ{]w5Lg}@ ۔/u}| =L&ӥb,x` g̘q''ߦ|Q㮜'S L@I)_x+g2zjE)rDFl|9t~EsBn>J}2'WlqbnWuZ~}]6+vo}: >)ƞ ܔdɒ?~^fՖ}r]ist/+"cWehnne˖E6TSڋ>||@M+z?|TԮZ>'P'Yǔd21dȐhhhhq*:@XԮYN&U}z7|s_SuJ].<~{ j~Еi`ͱx∈nErF}:~ ?}ɳO:2L'(M#9nX4}nqqEDznc4˗Gccc6}ۻzV汧UϦa]=F9  >5U: (#Z#QCCC455mt1bϖǒu}r|y7X#HڵkcΜ9qw9U;L*@$ -[S1:d20`@̚5+"/ @<:;TeWdP C is`8F={FSSKl"uymaOwKKKY&ϟiH@MEVK=˖-7 >!I(Nk1HHVvAܲ?B^{MWWR=xluuy]X.})1ѕ}k-gfv*c\c6B\^c{_9޺zv!\v>~r}(vd2f7owf˿onut_[^{ݝ>)1>r}ܶvW\!_\%ml[Sm.bruslo}M{ϱ-nc{Q{#mmOw { :zromnW;clW#ן7xG-~|qkKW>u]yw>qgg]CW?w3PhF$ f4PFQ2N^< *d>vxg (*~ΚZ t@u'$H #@\ ! (VF$NMH5jPy@0 q@. qa~Z`m H@3ʹ5:Ш nF$ :eTT7nx4TSg4VF@y @8%PV!a @3*cP8F$тe4 @1rc@ @'P2fB'$N F@ @$z@6 q1 6'$N I%$N iF<`LH@ͰJH8cFH8 q5:@ q5( j8d y@ 6 q@ @XT @`LH #$N6@J @H8:`)'@'2@T5 qtQ@T+ qz@T# q@ S&@ ȃQ@T qz@T: qQ@T* qD@ ($@ (P) @PdFPn@ @@Mfv%a.jym 5(J 8jІ-CAR&#()@5 7P#j&3u@7Tk$ Y}T HD!R@@r, 9 A`@ 2sIy 9X'(4:FFF`ODڳ]6#dOaQA@@3(;}Jo "@8 l Hf:HP^PPb@qxňg@He@ SFT7 q@ @'$NH8 qPfv q@ @'$NH8 q@ @jX6@'$NH8 q@ ٬@I8b> q@ @X'Amv?DD2S'$NH8 q@ @'$NH8 q@ @'$NH8 q@ @'$NH8 q@ @'$NH8 q@ @'$NH8 q@ @nΟ3IENDB`RcloneBrowser-1.8.0/assets/rclone-browser-128x128.png000066400000000000000000000177421362250633600222350ustar00rootroot00000000000000PNG  IHDRIDATxy|LW' ],v^kjWVQZkKi*X jI$AeO7;ܙIL&̜s̽ysU%.qK\._K\sAG,dC@}*WG+#≃}}bE5DB1K\/)Eb[gV;gC33+icxʉ82PW ([eo')uׅAĝf* Vjy".qY;ܹ3x7Lj7O`.LA)SDl3ϡM\Ae Lm*jSua dX][pqB A\)fį4{6GfY Sulq!<'2Q(qL}oW>ҏ2 =]QQ;HE_qVUQV_oR<_{X,‹2پZKb w1 5Qpyˉ+_PO,PTP98eBoyxS<B&n-rs[ |+ YHxV(&A\9EÄYa޻P-Lب"5c7y{F(K} .x[0&d4otN/g\$6sT$Ћϩ~ȜC?GN=Mk(,E 8q1+._sy|D@ϧD2>^\t/"@U,#|:v`[9^3 ]Ô޶QlPT]\pӓ]ʝ9?iI$f}Wx6@סB(RLβٟ[’IF`m>$PZX>}#Kė޷G Fa\B:RΖܐ|*_.PĂ*ˣZH_ 3ݿw<ە/XfPXHcc݀kK,ѝ]aн|= P'[]P Tza HU@E}" @ T_ BS$2{[(E,ۈ$V9rOT|ՁyY:m=Yl71paS t+hIP^5  5+$*Y(ǟd ȺiŶb ?rXXTM<k=1bvľcw) 8{#׉="c˧-ycPao`+{}ʦ봳C; +'5 Cw/#BD3Kp HÿőFL/ ۂ)cnr$1Xc^# ]vM¿c'ۓx$s v7o^dm&QPT=wGFS\|{O&L'N^C<2X.#\S%9ŖRKvH=bTB!'gĥ_)(&1+-621zX?/DKәh;HF!Z"ѝu^}6qI8&gad8/]3FY;($~YsPD+3.? `OdԿd1Ԇ%!Ed5q;qv6VHц?0`ۅk8XO@u+#338.T {a6Dxk"bs SfQ+2l!ߨa.P siLA c3~- ;T@m >|N?41M}.G6H9# ]2HE[fw9Pl'ӆBĠgJ ҽ'.ƩQPg"?a!+ԑYv"2&%UÉ7HPn-Eg@Z8Lnoty S_ hYT|-pӒt-߆KoF,0kLӾO8g%,WFؘL课S+KmbL|gr]Rm %\9gLPe ' SL>:&Bݯ^xm+(ͮjL]8 1 t!yՏ~CW$[?uf ^#r50t.U8\^e1a>Z;Q0_v/4;l7fYt02:E# R k`8NB r1SR[Dмjz)&] 9~RYBS[[(T{J b@槗?ΟgI&VLO7aW/&cCƨof J L߽~3- ^V q#03!f\:VX&O˥Tٮ~R5*q0(zm 6:Wщ zn>Z_?3'^p:0 =MBL4切НzڌނR{]HLʮK$w2ORU>>E';%-ZN qhwo+p-t .W U8sBA'A{P.;y eN萤* ?$V$ WΟ}! wD>OIVw4/X0ѧ%[]'ގhV XnF]A-?|A4[pV^sΊuK&qbr qu@ Nw$ Li ;Suc\oȬsP%:H3mTIS |Ll{xV&CjQI~*}@\C%/tSf 8[&y^ߘbd{iy{hxN"R2Gx^;.ߍ8goQ+/Nbng 3.gȜ0a?1n#`I|YQ}TJuDDf͂n4#.By$I\b^W3Vn\ .h921A[LG8UÉ˷_ZqE8h߿J A}]Ĉv`s^dFTQkQ,@.!> kb{kp%yϖVLkX"|u WG*C@2#e'/+0f2Ga2Y1֨c\ y|Kj*A'̢|{^ܓo,3&1e&LY%4<_ XL~AD`xq3M}zYDQ|)c 8×؁W';KQѵZcI'wo)Ot/(NHK\gֱag] %:I̵4/; _NT4Bw~&ܮ1vLLH=ktc Vѷ?e+ ([>@%bSrAoI[Z@Q҇CN^%6Yja;?Qŕk*nFcPXlcy)H]ćPw'Ӿ/~*7^(ܟ0  @N<zп&@_ó6=LQ. >(v 'Q[~g_ZĈ 2cǎE*%‡x9 t+_ay׽` L]$$6~ nTc Α*|) ;"B>r P.ˈ{NOKrMA[!_X^#+LJ- *\]^w=:lNg1.~݁w=#nߣrfZa\vӮZ8 =|4%@g.(7"8!ͥ&.ef]*{#J ZK>N . ~^v&Ue~ϙeƘ(p98d^< )Ϫ'2if fߔm*U?}l7Nwf0/12zd\E6ěƅAy7冃2 )&klߎu,WhLɷSXN'R(;x-bs#x{ ,谴s#2^-^FTF>j6TzQsCvBBI:6|pډ,];0l>qqipDYz̙+zнLj܎j)GID9yʠҝSDrɭNDI/)LG#,4ͭv 5/ha dJeܒXk4a>CG΢) cf =f8#S, 5`\s(o˷Nk[JT "+cU7A&$*9qu&` ´ApJNlzAQ:09BMZPQ?ݲMwg6đHco@V_*K/5B OId}GAhBpo&:hhb#Y/zJ&k7'Z&w,gL"p־ӓx}p2\Gxs0py UW|ij*02bjGht8@/l!z,m$n+3L$)BQ7~ ŷVӯq7Mn(Rj")a;V:8D+-b}-p>8~v.{m9o`yC kUMH?A|Re?anJ]w9#j2NՈĉ߹woR2 տdsb)0KV@8Xni#EC#nK=cHqHi4ekldAvr7 K"Pݴ>xe:vG22 Qy BAHvOk5nenftAiY2pywz ՈOkM 13>Jd˱ݎ -TmD 5e ħm1/>=So$ŇNMIM6ĸD78;"AneW|>B9*sEq*lW*qz]L {l^OFbCU[<CZ7J0V!+(t]6G蒼7&`Ľ]my;=wb '`c\ngt2#!tuÔiğFC FoUE<֘7D84]8iw*lA*ZBsW G @K ]*1ahw~׻/y5g耿1Rl7n#"K[.1'%4sp ڂ@x(ƔNmώF  0J!,2Vq9 sV{AP˴"%.ܪDIENDB`RcloneBrowser-1.8.0/assets/rclone-browser-256x256.png000066400000000000000000000216661362250633600222410ustar00rootroot00000000000000PNG  IHDR\rf#}IDATx ؖcǟzh! u $"B SPD1S"dR#d*Q$T Ҧ{?}cnWs?s}^;}}K,C:tСC:tСC:䍿~1СC @)P@yPKϔPСCKk#ex\ ځR*:td%3sg/z5x >ʫQ|W@^6AB+oyk1!UtH]t(w+k[$=`%n-yfCsG_*rlyED:"|zk>;xUPV@GԄO|,Or0EE5&y'_)&)Si`QP1@u5#*~&0ZM@G? et3@}Äop5G_:KPW@?,}*N}BwMv!|>/U@op{Ғ_+pYRF[Wُ "6"xuu He_X4p% C]}3BEjOaF++/HEI[3=?i;& :;ˏȿctĞ4K?.j2 Ďu?ۢ (0gr9_߃WͅLr"4~\ ,1asp9w-ʹS4 2ֱ+2*m&!-;tszR?Y\|tq _4Mxn`(@G\'!6O>8 ܬ d?L8\^ws4|L%NR|YxO\ +|LTxhlIv 'o@F9;Q'S,h,M]:l2L:$K}?G}iKܹ^uDg_RwIS,S OR~|e3&Ѐ$GA}kCKw;b\L*`r<@]*P3(3(A @}7dZ^?B|l幦`ل.HMڢ8P7 L{Lwv"p6H DN;Z{c7 4GW:HT%eahhScJػ̤ÎTK+.%mi[fp%W^dS {wt}y=!͔4GgpDM8^RLQ/1mߊxqvL²v~Ǩ_%y J:=U IVX: :Y}Tx0?S ԉRLEzAߟ=,a@? 6sC@i;9`+8XrG>D:®q.E9HqhZȵqip\T  |5{&Й)t&='Ϣ8W@'^/3:rp [╂jL'P#nҟkI:@8$k_7(auu1R}WJT(LR>ـ:f]TuѠ ׂ~`0 GOG}1=< I0 ԓlU[K2#/xyph` ,V{AEƫq&` *}E_y`5 H @s @b1ק\:=k2F fP'b fDGTmbm¯#'`KlB߹ },>}7z$PC \qב?W쉘l[ c`N4^_#!:/|- (?݁㤱 @ {¯f:xW\Uf&`|,gw3"pA_]*6 ?}oZ3#`zZjwxJ<*f#6`}p 8@M u/ٳ`:-6?dK"c`8T#m0*̘bR xm*,77_ m< W٬yyq,]=p(98_582kI5\y*ؖw%:$) 96 (/0O?~_5~D͝7aW*X S@~fPA/i@rPI^RsjP߅ſ:^4!`]@l@ L A4*#`,G9/wC-x -3sY>0N\AW&3 |2 sneX2l0_KgI/wrN?O\БWLG1|Ṉ⦪ $ fAQ&Si|;T+o`> _Y^,c>wU|Vƪ=q`M!@֚ϲL^v  7 ,h T sPn c,?,͑Wg[u;#~:h*=[W+o"h28ꏾķt|p&#Qa1O-qOK  Xq(QM?~Qg@?Ʃn+剉f]|,y /"\3.Z_Lͣ=\}RM箿 j#Sܡ|$s]?+0 fgߖ{aWQZ|:_Zvϰ@2crv`L` :&s\Zd?7I} n!վ!oJd8/~&zEAW Bl*:fpC9O/bq~l=C o>VBy-rNO~@,ʺ~Tfe\^=p#_I~ǽy˄G9G!{&`JfsPxDrȵ__oȯⰖ3chlXX `* J\Ht+pPM'ZJB\3Q׃I.䊽 b!,)Vk/o{#qC[$UYf`?0Bf%8&#_@~Z$~;/#0 gb!Zwc8}VҵݜÐ_ 7*mU\rج,}'cHԵwpy*kA , Z\L} ?C܀>4߇ _' ˉ46kp:Xǩ OѯepcN5PWF%3D(wo6ZnTz+Gg|}fDL7ӹQ-ͭXTskK8%ǧ 0 }lE\ͨE> h YjOrpeno\@*_C %_53UŮtՁOA3S8/B5)s\dTF 4,+}+w, 2c9s!Ӕ 5C>hc2|xqP"A_]Q[fЌ ?T &[ ~N3} Ncn!XJ FU97<j@Upc[ۼ sDk PY>c(UdObuQ+j1ƞ?_L*@ c `($0 2,7~<:rn|K/M X64 `I2qUy5j5;ǽz{k Ln[_W n/M1TV\ ^V P\uGrpS?XxGܢ\o T9"eC&@6ޠ o#ſxTe5\ls8~d@{\ grtw5ude+S9 (Ņ7|mcޢ? 5_& 2L6I-r3iWj:>[ PrPwCGM& ]I1N+Ptcq_GMRUDך17Ptjm )]ip 4{;sG`1JCq'׿>,)&i!0c!\6 39nľ'Qy z?TNm7ov3Z,׿ x9Kjog7[υ[_zc@y5Ӆq+I_o?ի㉱ֻjL"%z|9.~:I|ť&;۟$O"R`|)o{a`) :.~jX'@Ÿ$#"N`u'^p^ ޮK`keIjqSMJHVH] 21V裻kwe9o[)5lic‚(b, X6Q6njIa;Al%)5uIӹ @wx((@ ŵloo95}aWa~9Y-`W ,0 j nv 3m3Ē?]r}}l$X s3&NMA&|5}#mJD0W *I\Q &+rQ63(Ôl%]aPiNP[uws{Q5]v@\ 3)τ '&`֮fBbY0F@<E|qhs.x`(x` \! zs6yTfRJbW //t+7#T2[ tl @Q)fqpwFb|~@ ^K2cPE xe DA78qtmG@Wh +|=3_+KK{WտN3R*`.Y\>xoS\4@/4B{* o\ wnK\0e9ˮH(LL1Za}ơRѳ|~ j"x@~v) ǯ#o ]Y&7M)翇 3}Oh l0,Y7@ ;9pm; c%PPĭI9i:i\9l5.|L`#1C(u j[ܤ( 0+s.0X @ԙbuY@ U!})F00 g@&i3+ g ^~3^ O<= /Z5+gBނ{®6:1:4Spaf@L0v>Gc¶7b wN)RP|ţKԝi8d=ϡaɌ`ЁUDf!op՚< @}gq\/9b/YZ\s$tP7`@c#0YSY\ ʚ ʼh &%EZz7 2W`op5bwWps|и2 K ;v-n =k{M>uC`>`dԞf6ԷQ͈@P1]+nf+4`mf\v.;2*b@wG$pyp|.dft ?:w|o4axrHs䀀U{]'u.aeK>[8)hѡaJy<HP>(tyrV(} .*ݜX2qNz{+r+e'0 VyWf͒ AtpP|@snޚt+`-6tOLN4b$uNty&N:1 ks47  =nV!DzH|_NKT9IRn54L"xRIqW*^a9d+Hx:% 7d?M,f#Sxt+БU_i7TC6 P|oz (LZcMj(b3+@$[*rDߣ8oeTE:sIL??Bq\ U|/TCڪ%9h!T4nR&~i@!Z" VSd̀S>MV>|t_}#Xà ^d+@rHxUѽ3˜JPR0B5sc{۫5CBgF q JY/$+L thr#şLZ): Q28HTdv=&@k=4N *BS0k:svgi08c`ûS09p]! zCm~+(40q%Nz=˭m5ګPWAL`?-]B[fA'ğ  Lpp &o>z:zq'Y4*5~*7 ݩ ukH\Vz3ݔƙ~15ۛ7>&PjPNwN/3*l &iAz;8@jTg'sh ?3fxfV3@[V3c!ub*&@gr*贖T0/~׀ğPZi(x.צOJsFxae@q#"Lol֡*]}~&OKv #§RPI]qYY2>_qf ?E##-\k`g L92(}n 6qw޼/X4ZuS+cϽbq~@s!4|NœS9(=~«`6w֕|?{L(ܾ|-.:/U Uy6ܭ#W}bl5[`L4jw޻lLE^Vrp"#2a T({_PcȰ;(cqe"_f`3oQ :IK-mf \߿ |zzA$LgQ<3du?U+݊ek@jn{*v#1>|To ?l3R2H|NZ?q̸Q8d[ZPj_'XܖM'v,0CC|1oQB Ii4FX.XfC6(OƼo*jstc7|y'__ᴣ-eu79'mn]cOz >.N'T撵0a$kf$[O(J+5N8]5 BD.'F! cFz P澤`%?mK˙>V}*["& [ rU˂w>[EJTH񚫀F,p6k~is A"ZѳZ%E=Ÿ71`wP/+y~% vH׸n6PzB.gs Յn D!RwŔ|(uj|PGZ[}=yV=Ei;Ƥ*;, Xw0ިQP 8{h2kmlVQK*㰳,.Ro>5`OZFdA0Q˒z9f>CTM4A@6$#@TG*+i+U92.|xJí?HeME+i5὚H.AܿˑaL#Y-7']BBک2@/qV'J֯=Hd5 U\xLg'x**BQr,UI~M)' 7޴;^I[Ҁ@Ui{S?@ 68UDfԗHcFmIW@`a74ktqg@4:WIʿ#Ϗ!eӉ W@Z$֖nJߕ~LD5WGd̀9@vU.)c"qnfIȻLOf/%OPU"4r5Jnt^A} c7Tе8́tr1<[D~C7NEa۬e Xֱ&f]ߗ'p^d^ntHPfv]jHzܛ ĄbIcP~Ky<=w5x`qq-@zSoO>[#s ޴VnTAS #z#H8aS6}aa/0_G&Z_ qȱ"|SCQ%Eߧ8`K6=Z,̖enUb lV7FKY_Kmh~1pZ>."=ۨs łq[wg.gT|_ά~ԨeKs-ђ'3faz`qTZ(*:Jnvݹn-1}jv>~V,@1;(CXIwA,PG+ !K+kDUu}60NCr2 9xb46LC~ ^$Ne+OMdXVm+׫n=^S12+@~^{G!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!)>fM\\B!B!$ mA!B!|DX}b#ū_"޴@<@zS'"!} Q/C_OSޝ%6J /~Lv!B!B҂]?]SOKWU7 w>"OS-bR1x6fmT8}86\|t66f^^D^!B!P/$b.ĖgM{ qBR-WjaΟPwZk,ja8QW,<\]e6`Q6  !B!8=*7ͷ5|o䛋{u{kN0EWZ ni".X[wֈ "!hB!BH@!Zӥ{j~4Z3ɦUmk/LL[8֎<!B!9+&|~ׂA ^p* zX'0zTW4饍o$gB!B(sdXQ;bGCk7qGz84|XSļˊB!B[ ubbďtOuOT | s{1L<2H\&<ߋzzaB!]OW\{ڻ7fSXSgFkc`[6pB!ok&Ӽ-ݸrD-m LF`!B!$ 27w~,G%y8_\MV B!BrZgs^DuVOOoS;eSQ!uc Nq|qle6"ORB!-sgVvA/CwSBb~Y+vf!@!B ~}JcG_[+m#jM:@!Bk 5Dwr6F V [y^9 ^ω{m4!By73~D'zDWLGl~MC !B!~+#^[NSO˦ `)?"[ Oi#`n~1%+!B^o?rFOϸ%vωgpO!@!Bȅ6{/ե]gbj#9#䌞bb6 PB!5_s7QGD3=x=[B!BH0F{{@|fgD<JGf4UB!9.S^TBD C*><@{4"hB!'6KCz߷Ŵq64 Dtǵ鏉˾[г[*@!B4s_#rzG*p*qh6+bX@!B9?Zlu nh31@@ 3SSΦ!D!B|QgZĴ Ť["[yצPB!͛z=csrw}""fÌx8+!B?(q.MM~1sEUqf^8sxG6+"Y@!܅A]:zaqܝbM`S{n }x B ?P^t8jxj#":je[9hEB!ĝ%WKЉWD,lDDGyͭ.1 !BYgyN2FDt6W80[!BQZ?G@<:2wKI][#ٝC!B_\1)͘1AFD *~JQM}_@!f/g#"=|*6PF`B!'fmn=ۏ90ϷUohjgD&Bny"vbo?""]CGbs9cb&BɛS~7'ۏ;+{Ű58UC !B./hyX3?""@|B!¿stqΜ1A;Dhs qhqB!/qVu\̘SbճNcE!BH Ņum&`)LDя*0|ݖ6B! )Krw]""_L/k#`B!P[>=nȍ/O @!`/uB DDto#`b;>B!Vi?"" ?u-4!¿tm=ܯ?""o#`Ea@!/D9?""ok@=w]B!$P1 L(su,ڝB ?:a#N(+f|u~5(oh4qB!߅uGFLfLpR!""gsz-b> B!.C k\>cD$E`naH!ƎcLslsܳbe6p3B!x~nQSs19D5Gkא'Bɫ?Ƅ7DDnw.!Bȅ6bwK21CDD!Ko2B!$?񕚹btgFC! {_"DDDϩ:>?yT~H!Q)ňkO/^C !r^_Lxx-1`3 ,}C !_t"a bbCBF@$3 B!$ Ðpq1c!!"":WîlB!$x z?17 Y?CZBH5C."mfL 1BDDt8U6hgD!7d,~|cD!8kbKY/8'l BoدS3&L]k!.fB!/Czo!DDĠhKcK!Ɓ3\VGfK!q_kY/DDD 2pwq3B!{򇈈6&Z?/ e/BqB!SGX?PL%3 %B @u`.K1&6hcXX BߛLo`"y㬲b|VB!kjG74&0C NqF !?:|J\(N74&YZ0~_->|FϫKFb uk?{hp-D+"NBH07r`7__G[= ,C5ȝZ >E~wi :3k5 ! ݋,& &뼥kc[Ƌ'8JZHW@4VK1J+. }qvl]B7ޘU[ܛ-@-ucqb9-yh7ic@ _񒴙bzhXk'EB!_rbIωuzzxޫ&6 kN|NxblgB ¿v1@3A@IpNxGY<ͦ1P@nh~/1oC٤R  ku2 Lٻ"}71&2 #QaGu+Gߛ?2}ޒ5'!7ML3O=g}X"^ar7vz( ~77b7".|ZWDj~b"X @!MPP''1yc2>=5>zoi_zЭY'2!-0\B0T'tbG֛D=VeMMsپ!p-4ݫyf2!7@"^jSkH.!xQ- Y~RVB!NPYw73#Z㌌Z]17$ў*顑3'O2b͸KْB\0 ;#~|CK1jٿ^1|#o6 F5k{̵9 y-!'ARTfG)љO?>mVTK圭`A= @!IE{1#fk/7sfe@)^1;9O!Io2G\V%~N˛%bW|Z?[&,Fg#͋V*JB!z#@&xqơ~?z]EsQz DS1+!8bX@xFstػ:>'[?v薀O"1;G] B!%~ B!ڽ?8Nfo$>W"_ޮ[1 b06 a< o|Ժz͉~>u )qm%;牍BH MBI ܈z}ޘЅD!7^dUy%A\W-|om^+K!PJo]π?U/YOǝ|;;B|1S[i2 c82]_%{ &q)' Ͻ$4!oN@s* W4OB|>+.9hDB+s.x {&ĒBm<n|gQ1>K{3!/N7-_0 M~^2@›Bn\2VE{^|CiX$ B!^l>..oYVv(kQ< )=mx2g^O?!6xiBPgک 0řXq*ିN6xCSB8Y]61@HWိWqy{!/4t'Q 7+JSPu:nc%tBHNi \t)w?!#yޡܰZiiB@].@T[F".JtG[Bpa.T=ԲZ>'!nj>-&crx4 !a >6ץ0q"X0ycB.b >FSlx!<0?[joL @x{y2 B!1a*ʮ;q]!_1!$Wa+?ls!\Ą`R썀ax>{]xnsX)Br1!!q*:qb^ dw!4@ѹ(ּ!$w`܍ _T ZP%6o*y{?,.T\_@zsxxxxN R扽GէU-L|GB uM=&IZ ']j!v}'i"U $O\UFH1Y˩ڈHIrwu d'kaIFEJSPOMտ1uzNL-VoQTO~1M~Ҵ1PсI@(9J @!ւFE ?O<-XЂϑ6ZԂL Ea +[_o$:75 ! *Ct麾X߬N!TG_YL(ftD g9Cb-pD8 B!N*CsVGwߣKu ~ q._OG'0 0ypa@yFcʠDg B!TM~h=.UY.-׽u ,2(1x|q!SgZ'B GtIŶ) !z\>[зm6yBXAXD GtG5 B?,SO5nz:h=-/b>"^׉27|N kůF-X@PD5B .w[cm& Kv F#ӛIN}toZ C ^o|?|ϻ U\cšw6+ 7_~z]#"^)=.5aH$?Z\G J {b}?mds`Z0XuM# 7 -v7TwM?"EdrB$o3: & oWh/! L=ybǁb?wApqZGD&rM ɢe-oF|zGoCt450JWB_zhV'~NLsL4uī7\WP* q3ߗ 㰿&qbg:gYl|Do@ Y4q_s8&Ј2pذ1D3BdQ%Qbf41ߞ_do gE|=y_9z8j1i!"J֏ (k6bfe4}Ll \y95QYoΊB BaQ81Lx\}6+]Z Kf(qg7|Eds{@i=9{̅s1zƳbtcѕ&Z?xD,ޗk?'bAhuho׊?wxa*Th T~.H[9gKDfLjDX jc]oP* b^yꛥ ?O?(^ٖF ?by\㇈?a7pқ7VP!|X GPKYm1\h7_ӡ1HL~޸\,E 1Z=hʠD?(|S/nj6kk9 N.Dnm/ 0""ΟZCY-Nz,!5v+713z 4~ Ʌq_?S#"觷|!K)0~v?K|зյŚ ?$ :a9h~Ql3_bqͿq_u#'1ÕīѡBHk=⹑8D7+@\o1N.ggtU <{{eϿkv4H-/"~؄0yO8P HkRkϾ `d=MyOq nG6 4/ VoDqEjw{ 7a?ӾCћPkAW~uxO ~k4XK \r"""feIfЅq_~\ݛCbe>?7)tt&p{SoܤO1;Ds5r-K?WM)sX ϛ+;=l|bx$A# ։Xr""^FSQG1T ZT=[WN yŪ?7o3}BsY T?:+0]|o'XDD;?GH|/bNn%aBzqϖ~#f{GF# U{CJ\#M""G*^vHV8QatC}?Gou[)`o H#ڭ4!,c."""bNL{B{ o?jqIC4M@f^;E=vkAvUE@rօb&Y?C[3146C"L~^ Bp_@7z_B B}`?:ō@4#m@-R|a&aNz2[|) =l&>k #Z2*~EuK~TW< !"EdSf1'05ODg~%^R hka~1:)$mt<-m{/[-n(cL""⅛rx{Fƛ@~_CĻn0OQ^.CUYw^-5>]o,ey`_?91i1y&Y?2Z%>׉87 c3.#"bޝJysb_}.{segU+ĴC{yg*`Ub=;[/h#]Y*""a/i5hX _߄Q!5O2 jP0s8qF'mĘX pa h^t|և6!""]?kss +FA5~j-Jҟp=@$#r|!.DDdI1?KaK@`Q1-^7-JP04@i|>ئiݣ%}/Bzأ&u?ٕ(ksuQ8Q@{;K/0 pB'ZT,j^jD7>PV K={~sx!UǝͼG&Q8{;̿jF C 36erM`K3|N8/m s8rP4¬P6FEcd ?okZjtPʠ`q` 6¬*zvYӌ54Z [)\Epi;ߗ`m*"" +^j? z/+&^{y 6S"fR] ys""^Ƹqs}q&Xl;&s 㚿B>%1(L~>XVzЭT!&;WĒaķRhsN]W kz<3/&롘\ suVq2#bޚXN[E7PB:uzV&? )Am1KE޸`U_YmVRg:3G?U]5u:H3szZA>iBWRqX*%©FiFJ~ԸrdMF7¥fB#HN>C7'Q ˽'-?JLЂk P /Z\JrApۦ P⅛G:(#|m-8 /kS$R!/ "o61F1M~Bɓapg`.4V|6[4 z>n6 +s}Xkxϻv1a3b^_9El?W;?Bj 3 t ^q]Fk<7b=ml x ; >T hoo78_O%rx!J|Sb}}742RTNl-z(`>\TxGbM~>8xi| PBH@4¬[-FLԦZ?"V]=ׂӯ8\C~(ւ̸1?}P4^_ ?!Jeb?3S|}͟VDyn,,9gן! 7K1[S<ߨpڄЍc wr |ϗ%>5YPf 1. @kny#7\?_x| z=b+?5!8c-n*#-apnw1 (IsűoOf} (sz_RSb7YO!j)z@PdL~x9% +2}' `rBou6<7M75  ? gC[+I?)2!E#&4qChKaX?o.^QYd0g n+'V|xG!4_g4zSR@9?7lI:K#t+\ZLmDIJV@ ).() 1x].[m^PN!C0tf? iGjD1Kn-lFϾ$0  B >Wc_tR-(0=g`#rkxekJYX,^aQI# V|(2( 1x<V;v+<7SLXLAAY}I$?! _ Z`vc^?4[ B40xMR$8]uBȅ4Bcɖ(?/{ül42~GQ %R M~rS C ӬӆOm׳0(C3!'ӌBobi6F>B!g4-Uk>lL)>O%}>۳]3D!lDmӭ qA1$hLFYq71?!N}S>PHƩϡ?Lֽ]7~}=wӄ&8f1<{WDK=8nc&=ȮӅKf4xBڀ[9_2Ŵ0*T\]!}28fe\ThK@@>]BqV# J |GiڝZU7;oDaE31j?!N}G$ЦX֗Ɵk$GVyp'@AwLC!; N.&n>c ` NrbX*+!nHwl%5^H 7Q@s=ؠK q+1Si8%`7qVS%<ѹ~'I )$ѹgXjGOHOVr풣5hX6Gtb4XEqHxΣ!O 1t B}Bix@€ӊq8 !ęUأ\G܈[XOHO޴w]qfGF o! }xJ3R@} Bv"e\WLU~1@%2@1Y @qfs 1}!stBM|O@@e (ˀj3g>4~/B{js<ѹҢ) ѹ;MOHM­Jw?4|?ߋ1 8P{ϥ<ѹn- )$ѹ&!6a480&'3 /|!9^iQNyHLgU_\h"Hc{N;-xk\Wc@&H։RI-=5s'eѓkoy5mD!b|{b鐵:ŀYhq| !9ν؆7:y8 ItSZe4(:1Ry12c\EČg%!$OEF'S4 It `M;mE $w{Eg\!>X'D{)\y`MX1HOit Itc gD`]k&?apM !$0]8 $:יh qb Y?yD-69vL6SHs]) B6 NJ /K,zϩŰ Rctj`-\W V@'>@&Ve XBo66BֳbɧhHoOfd1O5Δ[Ԙ%xZNsk|umO!@sw4ƀ΀~X8@UlT|bcBzkI FnJﯥDk4 NuS1Bbvqz6@ď mcZ<ѹ._۟P@כ!Zq\KJt5>o=m^G'r|4s+Pz$:Y/AOpGJq?:ߋE+sM !$o!|K@!ui1 Br;Uh^֧#:uHq2ZWMZBV-K7a+!}/bs `3*xh$ P8Yt@31.BkRmF%X|SacHdD8A;JBH L[s\;S ѽS,p@BH}⹊ bVZ [PSP]Z," BDX'4Uwe  "??d%!$g 7?tj`g) m_JD4D'2g@3xw~KEW=ZEsR@{|7 „< 8?KVPxi,(: g-&pD͟b!4o2~a>(>9k !/ybH0' ItǫWc% ‰>16!3 $k_4^\&6¨KI1'$ǁ K{7l;nY a)E&Z?/.*濔$&>RYFě@Bzt kk9Y GtI; vW剕o4{9S顀3`"q[7‰\HSۈ?Ţst>DdGbR  7!舘^T|47c A412 lqX<oD3y.8(0"x0\Ywq,9tCe۠hC<" 25 oFB6u`D̩ːDV[hqb*-t7ߗqZ[ܺǡ/{n1`xCREߌX8!w;[|D+F!~+o>Y.=|Cւ'^]l@C,qL#f̀x笟W.0O2_ajVDxje|b"@43&z-' 1vSb6gs{YS೥4oų1&X?%>nȡ+BMUL׶ձiD2ss?OǬ"aQ=|b~NyU|cXVA#8`BaVRYR/Lwo@?BO'zW>#Lk(vKY7ޜψL%h2+2}LOXx{tE⥿2xeĿ8}X+g[Zm}*ߖt+ : f[Đgla4H!*ԋCL%޶En4 qfsܸ5(gSk-0kˬCţ(zd"PM#kYol! #bҚ76e>!Q=_c8kSbv6 (*#<9FT[mn{2q)?+/N`Gʀ(Z]thy_Wl+fO"^.ۊ!,PCz\pl4y)Lo_i>K|_锳$LJ͎+D7JKƇgsX\>o)B?,1Ħ'Oٵ _-?PiS ږ`G^G  b>}rD5SпܭCɦ/ٛJD~J\8HV{$ yQ臯kG5w L$vj qM`a}cB k2==VDW tHO׳=B_1B?TWO>̰M߻Zq+q(`ot b~!H}^6\!5 wă__ܡmi%dyycn%}aS>q4]W}z-Dub[q=d0)@>NfETH6~# 2crckbDyO3zTh޼BD2=0-ωe?)[ usDŎ~إExc^_W6R={ek 芀]s9G|a_1+keߧH?D_{={p?4>=VK\nk $u[.cց=0_ۋmzݢ^`޺PE#k]ҿMN6>|kY)֬o^7 qhU]º2&nk wʟ1,RD&?o l>M,WJWNtzK1P\ }= < >[L}?gobigĽ/瀞2{z|]iX_RbIZg^W /GbcC*.2Q1SWqTGqa/qQ\玽;|[o9Ą1X8&Ro (~6R|A z]b%z]ar1jaS3QgJĂbzKBݣxwFƋYW0(~8ZH+ 'DD`;<+­P#""C8Kl NmOKk1MխDp:p.`h ""b`q`Y֏`[->[\}q6b|EykYX,]QƄ3 7"b xpX/hDyy%@u^#U֝wDDDDkQXruоiD['1FD (S[!FV5떀-L@ѿtË|KH#N'_kn4Ѿ&DiqUQDDDDk7xK,2g0Nr{1o Xt 4Qցb&$[㦉m3 h*WX|q6nD@68qgc`@DDDkӬ&a։cIu\PSJo p/ ۊe=P#"""tCkk$P\og⍈NJ iGf:ʛ<&dkZkfwp0[z1`'0aADDċ;oS?>O xXL(oL@3#"$_1x{%@6.o5 &4kt?ڭD$y% J|٘x1GD $f+H/7 #ֈa₈4yއbKƼ# ޫd }5kbxە5{ߞhBu?ݧ/|ǖDĀt8 Vbq&8_O̒4Bǘŏ3FD (Y{z5 pP@]"1ADD z㭟R,jBm# uY.qTn @D dg#||8`~< ""i'm~*s|7[B0Q3qďK ͒j߭1L~^.^zhDxy%@%iw @ DDDkaXCF@uY[q['_'C_%]N#&[?_v-%1 L~>5D>xJ!1& L1&>>S]% 837"b@o]1xM[b/dv-XjaTjD['4&qLBmneoo zu"""zm迲䟊[77uabс%n9 DDDt~:w.’頕bhc"Ƅ/&Y?/ 5^nX'az8tQ?IMu|e!Tj'U7"b wDZ'2BDDt_KsP]Xq""Vh!Vc4>`ĠuP}q,,DDDgXQDz?~""3:׈^>isBzobc MןFǥb-z/-YBDD/amaև!""K c3~ןC-'Tco:sDDo{X]G0D"bBy/0pD2Fl1XxI։E N L}~3:b1T$F@~DlY""|m pіXj$&hmr Z?_}""M~N[|`0 l:cr ""b5Ͽw-6S<<$i> "֦}wx6u"DDDZG|qHN4ݘabFOrov 鸬2"""c\]4 F@YHNQZo<|5&&鵁5N2>DDD̜$=1?C9ݟpSGDqDDh\Ӛ2U8gD%9Ktsk@ @DD BSӵAWօiTė0c"N4 &興>18s;ǍE!_gF!DD`(3}11&(H? e^ԣFDD/yyD&_h4 X'2g;l֯H;/$Q edCo4 :"?X <~b:J#?"D ? F@uB8yN@{"ѧfX?#7F@KřtbtF""_]o-Q'6[R*)o4pF"OM~NMA -o<8c:QJ7 [/x&'Mǩ_k?ŸsC ҘP>'ާS>x_l\ F" z\q11cB>oۊJ1OQF@7Lwt@DDjβ]+v[|oAx#cbv *""z?oGǧ??!kyn}Vܹ9"?Xq.F@YWJ<\Ԙ%0aED<0*sS*ۍW7/1AGDS3<|2TQ[_|PE-a6 &mƋy.!njxnѽ\g3aMd⎈F{µyִ/DD\h3>"lSR獀RĩŔU͕Vo]*yBV>DD4~!Tp]# ӂQ=mm\e0GDS363![i℻eD,`DĠs"q:~\a/'wW4g "\ݪUz]Ob߇.i\GH4=7jE_L\N;[ԥ7H?7=/lQg ")[lN'$ (Z1^ @D6 owU0zA9["\QQ,#Ĺ[b#/ր_nIٜ8x?[e矟O9J,xyV'L`#"zmg!zL4p#" A ?!iyh^ N)wD/̿4|ҘFx^ z✏Ŕ{h "a~?f!_aYyqs!DDIo FE ؿ8""a~;{.Fh%_# r0;1+ԳG]!o fe@7H be"W4 Zlr-F@ħb׉ֵ @D5C3`<,J={Pݛ+DD4LC<.8֦!d2 ʀ*ĤoXx!赀 ]7,P ã6گ ~+s"⅜ޟnRo !2hDU{,ì21'@C1NaN7tyBZZܱ*wW#"^Vzb6 !G+ogV'S KMNzʝbq6L.ȕ=OT3?L( ѥxW+l B2 \]&~uz̈́9]V-+nn*mS~oq1/v+( ѩ?rbwOO ԕ'7=c O_^YDĀf%ӁSony+ʵ}ox#g3N@>W⌑bcn_@o>⫷zZ,D@/#?L ,'!`s`>^) 8" ]+(Kiؼ $6> ~OEJlSgl PFB66ĥ5Y 9L?Q?V& m {ee@rZz`em'ąڰ aS,l(["^)2Q\+nVEn_aZ67蝂? ۧc9f06ADڞ?f*ݣM-O暴@MC@obR-ED Lh?FXxMMO!kDx4%rbК;IAW M,!'g BAgbmm ( ~Bۆ͖b麄4]kzI-m; |47oI5 DtY'~Oo-Fz>B z*w]"0^;>[ʟz߫' \`wJ?P6lZ4'{dSPPX!Yoݦ=~BqFC f_X>Dab‹{ӈ[ʟKb"6N'B _/)/ p}f?Yq"GĚzxh'w4².JӾ{GMB :Bs)yO.!fSE^ܭh ʛC/KbYBɺ`Cª<#x_N'M?Oh)K|~=sb6O:@HCn7CQTY8[M!FAxߏIp=bاd ?H qw-XRְ1~ηy8xÝb9,'8oig *[|wZAĜſt γnü'@#bz؞bZ[ 0xM/!,s-7gS蟷rn dR Ӓe6}s9C ]zA |WB~?.:˛}B!hd r-l|^|c6hD4y[P6X!}E 6Bzڊw5o!VyS`!/~E-_B-J|'@Xdݠ[ CzKo7Y9?m?ӌzHı--h# }Bm l@ =S:=k7wGiyx2 geܞFBYc =|P좇]YTG<7癞R%Q_W/k|gb|åOH9k /.+}W|gg^o40=7@<\$%^2gBAKK6+'Bjpp.^NAo/fqju8OLYg>!odc..NK*NՕ^+_P''o+k{-/Vj5BBKQC3>*ެтYZ=H}1:MեGuKJ ]CGO&ҺT">!i QPH Zz+A=%b)ݻ {?+bt+J~VRB!o m^[XXUaŏ{w'JA靝gB!|~'{V6@o¾;o ! ov0:Z(6SVMDx7J{T[(iNߺ1-[J=+y3$"{B!ıЙbwK7j:ũSPqW>C183SZliCQ4 x0s˝lό6(3yM#qmߩXNi? KŒyⲭy|_bEh/DxO^ 0t"/TX(i)qso9651w\K^MNm*$3^6B\BÉvV=bzs*b-ߖA`-QOTqCg%Uo_Jאּz¶CQl[ݗr4f`]wl,~5,?3򺒹X,Ro}mk'p-NXs CCKr]'F DGnV~2 "p{d0aEк7CNogmU ȳȚX"LVÆ"&; |p?ng\*iu KyKD.pq# MOVoH,eV(S=>.;PVef#![B\_&9sr&@,Uَ c))7# KSrd-LD&p21Sɞ Eȸ6#?aKcG#jzS W ^֏Xzm&Xa+5XCz*,ʓ * WlRI8B30LSc+4ܞtگֳ*\Obf_,rf<3/wK  [_`K+ju f;!ńaKeT#-@|[3 Chn^͚r"K Ǹ9V^ @Q7i|ƩƘ =k6M#t0aVefF=0RcrdlL!p9#T=n<4^H8j (=@VP=~rpP_?#՚T5c'VPkV.o5+T~F5dT ~oO-#<(m*qポƕ+MǞ\jT8ɰyB1P8ASR46h޶ݩ:C)ɸ旎Sp}{;0h%MH߷ V g!(kkQH`!6Vt3ٵ,qtE-Ki,5jB: _zn 00#3s!qiՑl3D`O_8V([\(Ur*ND݄+&ƔhD|MoZ&fscFӵ x/q_Eʞ1a3!\3- nfRSf¯ <sp3`ݺ{}96p{!ɓϷaR\7歗nq>NHFT/>S d|KK`@|z{|{&+ұ4_Ǵ)<Цp qqMKQIlZPMەyTBh^j߇$`=3{ ffV8 Nx/Oƺ37T 3ef)Ca _eaL|`Fj7v{} r wQN ȡu@b Bz&2\?ny98ak0<3<<^(HRC@'2|+!Q8@wlyR@TFa$u2l0͟zØױj'z&P}(a(L)3U= + oi%3c;`x152 9} B/l`D?Bݣ9sF6V6ejs㨞zJoJdR.Լ[BJ§kIp:Կ1MQ_4@OQ)T#T>5e,CNR*+\޺vhM|؎ G0ߛR,m-c__9hXđ;kD!gwսDgcѝiJÝpn>#hJbRѨ p4ٙK]Ғ0ʱ.! ԛ20,ao(:Z ) ݁!\7r`C>+&QS4)O;pfpnOx!ML4:n^\_sg] Τp:QA:x"m|`J>{W N8jm?ct8.-'_7<n1Cuu a^bnv`f1G@>+ί7p8{cs fHC*=wrc؟E2ߙAL5j~>+/R5rT n><;kD%ldgDb;g卪p4VᆙZ G&mG@NoHww '8֢G8;숙he#SE:R-f2% Da^'"\asJOf«z#ɕ`9oQ#z{n;:<+hg%fWv۵,vF2e}` -j'` #I"з\t:p0hM^8Z7vo'm~~rW1]ًmp4O 7Ř YEunl!B >{t{<`Tk16C:5.:5BPbͺH -~O=B|Ol-v3lA%4T衬`.vVl i9t%{>02zG0`f/_ Sj7EU_ԤIY-)ݧtg©vaG~PѸ71[K!<d!{Skbrgߘ+ؙ$G9-z5 n^mJ!yr_zwyza1.aQkI|:f0^"UMKL0ugwЗ ؏M'`L|h|A` -##m )6\iyn;1/XÞ/1>γxd`g`d3-2_`SIENDB`RcloneBrowser-1.8.0/assets/rclone-browser.appdata.xml000066400000000000000000000065241362250633600227230ustar00rootroot00000000000000 org.kapitainsky.rclone_browser MIT MIT Rclone Browser Simple cross platfrom GUI for rclone command line

Rclone Browser is a cross-platform Qt5 GUI for Rclone, a command line tool to synchronize (and mount) files from remote cloud storage services like Google Drive, OneDrive, Nextcloud, Dropbox, Amazon Drive and S3, Mega, and others. Use it to copy a file from one cloud storage service to another, from a cloud storage to your system or the other way around, and to mount some cloud storage on your system with a single click.

- Allows to browse and modify any rclone remote, including encrypted ones

- Uses same configuration file as rclone, no extra configuration required

- Supports custom location and encryption for .rclone.conf configuration file

- Simultaneously navigate multiple repositories in separate tabs

- Lists files hierarchically with file name, size and modify date

- All rclone commands are executed asynchronously, no freezing GUI

- File hierarchy is lazily cached in memory, for faster traversal of folders

- Allows to upload, download, create new folders, rename or delete files and folders

- Allows to calculate size of folder, export list of files and copy rclone command to clipboard

- Can process multiple upload or download jobs in background

- Drag and drop support for dragging files from local file explorer for uploading

- Streaming media files for playback in player like vlc or similar

- Mount and unmount folders on macOS, GNU/Linux and Windows (for Windows requires winfsp)

- Optionally minimizes to tray, with notifications when upload/download finishes

- Supports portable mode (create .ini file next to executable with same name), rclone and .rclone.conf path now can be relative to executable

- Supports drive-shared-with-me (Google Drive specific)

- For remotes supporting public link sharing has an option (right-click menu) to fetch it

- Supports tasks. Created jobs can be saved and run or edited later

- Configurable dark mode for all systems

https://github.com/kapitainsky/RcloneBrowser/wiki/images/IMGdefault.png https://github.com/kapitainsky/RcloneBrowser/wiki/images/IMGtransfer.png https://github.com/kapitainsky/RcloneBrowser/ https://github.com/kapitainsky/RcloneBrowser/issues/ https://github.com/kapitainsky/RcloneBrowser/wiki https://github.com/kapitainsky/RcloneBrowser/issues/ Dariusz Bogdanski and contributors dariuszb@me.com rclone-browser
RcloneBrowser-1.8.0/assets/rclone-browser.desktop000066400000000000000000000003011362250633600221460ustar00rootroot00000000000000[Desktop Entry] Name=Rclone Browser Comment=Simple cross-platform GUI for rclone Exec=rclone-browser Icon=rclone-browser Terminal=false Type=Application Categories=Network; StartupNotify=false RcloneBrowser-1.8.0/assets/rclone-browser.svg000066400000000000000000000077071362250633600213150ustar00rootroot00000000000000 image/svg+xml RcloneBrowser-1.8.0/scripts/000077500000000000000000000000001362250633600160035ustar00rootroot00000000000000RcloneBrowser-1.8.0/scripts/images2ico.py000077500000000000000000000021041362250633600203770ustar00rootroot00000000000000#!/usr/bin/env python3 # to install "pip3 install Pillow" # packs multiple images (bmp/png/...) into ico file # width and height of images must be <= 256 # pixel format of images must be 32-bit RGBA import argparse import struct import os from PIL import Image # https://python-pillow.org/ def pack(output, inp): count = len(inp) with open(output, "wb") as f: f.write(struct.pack("HHH", 0, 1, count)) offset = struct.calcsize("HHH") + struct.calcsize("BBBBHHII")*count for i in inp: size = os.stat(i).st_size img = Image.open(i) w = 0 if img.width == 256 else img.width h = 0 if img.height == 256 else img.height f.write(struct.pack("BBBBHHII", w, h, 0, 0, 1, 32, size, offset)) offset += size for i in inp: f.write(open(i, "rb").read()) if __name__ == "__main__": ap = argparse.ArgumentParser(description="pack multiple images into ico file") ap.add_argument("-o", "--out", help="output file") ap.add_argument("input", type=str, nargs='+', help="input images") args = ap.parse_args() pack(args.out, args.input) RcloneBrowser-1.8.0/scripts/prepare_icons.sh000077500000000000000000000026611362250633600212000ustar00rootroot00000000000000#!/bin/sh # requires following programs: # brew install optipng # brew install imagemagick # brew install makeicns set -eu # input file: square canvas svg if [ ! -f "../assets/rclone-browser.svg" ]; then echo "Input file ../assets/rclone-browser.svg is missing" exit fi echo "Converting svg to png" convert -density 2400 -resize 512x512 -background none ../assets/rclone-browser.svg rclone-browser.png echo echo "Creating 512 256 128 64 32 16 image sizes" SIZES=(512 256 128 64 32 16) for s in "${SIZES[@]}" do convert rclone-browser.png -resize "$s" rclone-browser-"$s"x"$s".png optipng -o7 -strip all rclone-browser-"$s"x"$s".png done echo echo "Creating ico" ./images2ico.py -o ../src/icon.ico rclone-browser-256x256.png rclone-browser-128x128.png rclone-browser-64x64.png rclone-browser-32x32.png rclone-browser-16x16.png echo echo "Creating icns" makeicns -out ../src/icon.icns -512 rclone-browser-512x512.png -256 rclone-browser-256x256.png -128 rclone-browser-128x128.png -64 rclone-browser-64x64.png -32 rclone-browser-32x32.png -16 rclone-browser-16x16.png # clean temporary files rm rclone-browser-16x16.png rm rclone-browser.png # used for window icon cp rclone-browser-128x128.png ../src/icon.png # used for linux instalation mv rclone-browser-32x32.png ../assets/ mv rclone-browser-64x64.png ../assets/ mv rclone-browser-128x128.png ../assets/ mv rclone-browser-256x256.png ../assets/ mv rclone-browser-512x512.png ../assets/ RcloneBrowser-1.8.0/scripts/rclone-browser-win-installer.iss000077500000000000000000000053301362250633600242600ustar00rootroot00000000000000 #define MyAppName "Rclone Browser" #define MyAppPublisher "kapitainsky" #define MyAppURL "https://github.com/kapitainsky/RcloneBrowser" #define MyAppExeName "RcloneBrowser.exe" [Setup] ; We will use two different IDs - for 64bit and 32bit ;64bit: AppId={{0AF9BF43-8D44-4AFF-AE60-6CECF1BF0D31} ;32bit: AppId={{5644ED3A-6028-47C0-9796-29548EF7CEA3} AppId={#MyAppId} AppName={#MyAppName} AppVersion={#MyAppVersion} ;AppVerName={#MyAppName} {#MyAppVersion} AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={autopf}\{#MyAppName} DefaultGroupName={#MyAppName} AllowNoIcons=yes ; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check. UsedUserAreasWarning=no LicenseFile=..\release\{#MyAppDir}\License.txt ; Uncomment the following line to run in non administrative install mode (install for current user only.) ;PrivilegesRequired=lowest PrivilegesRequiredOverridesAllowed=dialog SetupIconFile=..\src\icon.ico UninstallDisplayIcon={app}\{#MyAppExeName} Compression=lzma SolidCompression=yes WizardStyle=modern #if MyAppArch=="x64" ; "ArchitecturesAllowed=x64" specifies that Setup cannot run on ; anything but x64. ArchitecturesAllowed=x64 ; "ArchitecturesInstallIn64BitMode=x64" requests that the install be ; done in "64-bit mode" on x64, meaning it should use the native ; 64-bit Program Files directory and the 64-bit view of the registry. ArchitecturesInstallIn64BitMode=x64 #endif [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode [Files] Source: "..\release\{#MyAppDir}\RcloneBrowser.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\release\{#MyAppDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}" Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent RcloneBrowser-1.8.0/scripts/release_AppImage.sh000077500000000000000000000111451362250633600215270ustar00rootroot00000000000000#!/bin/bash # x86_64 build on CentOS 7.7 # gcc 7 installed # sudo yum install -y centos-release-scl # sudo yum install -y devtoolset-7-gcc* # run below command before build # scl enable devtoolset-7 bash # newer cmake is required than one included in CentOS 7 # download from http://www.cmake.org/download # sudo mkdir /opt/cmake # sudo sh cmake-$version.$build-Linux-x86_64.sh --prefix=/opt/cmake if [ $(arch) = "x86_64" ]; then CMAKE="/opt/cmake/bin/cmake" fi # i686 build on Ubuntu 16.04 LTS # armv7l build on raspbian stretch # Qt path and flags set in env e.g.: # export PATH="/opt/Qt/5.14.0/bin/:$PATH" # export CPPFLAGS="-I/opt/Qt/5.14.0/bin/include/" # export LDFLAGS="-L/opt/Qt/5.14.0/bin/lib/" # export LD_LIBRARY_PATH="/opt/Qt/5.14.0/bin/lib/:$LD_LIBRARY_PATH" # for x86_64 and i686 platform # Qt 5.14.0 uses openssl 1.1 and some older distros still use 1.0 # we build openssl 1.1.1d from source using following setup: # ./config shared --prefix=/opt/openssl-1.1.1/ && make --jobs=`nproc --all` && sudo make install # and add to build env # export LD_LIBRARY_PATH="/opt/openssl-1.1.1/lib/:$LD_LIBRARY_PATH" if [ "$1" = "SIGN" ]; then export SIGN="1" fi # check gcc version on Centos if [ $(arch) = "x86_64" ]; then currentver="$(gcc -dumpversion)" if [ "${currentver:0:1}" -lt "7" ]; then echo "gcc version 7 or newer required" echo "on Cetos 7 run" echo "scl enable devtoolset-7 bash" exit fi fi # building AppImage in temporary directory to keep system clean # use RAM disk if possible (as in: not building on CI system like Travis, and RAM disk is available) if [ "$CI" == "" ] && [ -d /dev/shm ]; then TEMP_BASE=/dev/shm else TEMP_BASE=/tmp fi # we run it from our project scripts folder ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"/.. VERSION=$(cat "$ROOT"/VERSION)-$(git rev-parse --short HEAD) # linuxdeploy uses $VERSION env variable for AppImage name export VERSION=$VERSION BUILD="$ROOT"/build TARGET=rclone-browser-$VERSION.AppImage # clean AppImage temporary folder if [ -d "$TEMP_BASE/$TARGET" ]; then rm -rf "$TEMP_BASE/$TARGET" fi mkdir "$TEMP_BASE/$TARGET" # clean build folder if [ -d "$BUILD" ]; then rm -rf "$BUILD" fi mkdir "$BUILD" # create release folder if does not exist mkdir -p "$ROOT"/release # clean current version previous build if [ $(arch) = "armv7l" ] && [ -f "$ROOT"/release/rclone-browser-"$VERSION"-armhf.AppImage ]; then rm "$ROOT"/release/rclone-browser-"$VERSION"-armhf.AppImage fi if [ $(arch) = "i686" ] && [ -f "$ROOT"/release/rclone-browser-"$VERSION"-i386.AppImage ]; then rm "$ROOT"/release/rclone-browser-"$VERSION"-i386.AppImage fi if [ $(arch) = "x86_64" ] && [ -f "$ROOT"/release/rclone-browser-"$VERSION"-x86_64.AppImage ]; then rm "$ROOT"/release/rclone-browser-"$VERSION"-x86_64.AppImage fi # build and install to temporary AppDir folder cd "$BUILD" if [ $(arch) = "armv7l" ]; then # more threads need swap on 1GB RAM RPi cmake .. -DCMAKE_INSTALL_PREFIX=/usr make -j 2 fi if [ $(arch) = "x86_64" ]; then "$CMAKE" .. -DCMAKE_INSTALL_PREFIX=/usr make --jobs=$(nproc --all) fi if [ $(arch) = "i686" ]; then cmake .. -DCMAKE_INSTALL_PREFIX=/usr make --jobs=$(nproc --all) fi make install DESTDIR="$TEMP_BASE"/"$TARGET"/AppDir # prepare AppImage cd "$TEMP_BASE/$TARGET" # metainfo file #mkdir $TEMP_BASE/$TARGET/AppDir/usr/share/metainfo #cp $ROOT/assets/rclone-browser.appdata.xml $TEMP_BASE/$TARGET/AppDir/usr/share/metainfo/ # copy info files to AppImage cp "$ROOT"/README.md "$TEMP_BASE"/"$TARGET"/AppDir/Readme.md cp "$ROOT"/CHANGELOG.md "$TEMP_BASE"/"$TARGET"/AppDir/Changelog.md cp "$ROOT"/LICENSE "$TEMP_BASE"/"$TARGET"/AppDir/License.txt # https://github.com/linuxdeploy/linuxdeploy # https://github.com/linuxdeploy/linuxdeploy-plugin-qt linuxdeploy --appdir AppDir --desktop-file=AppDir/usr/share/applications/rclone-browser.desktop linuxdeploy-plugin-qt --appdir AppDir if [ $(arch) != "armv7l" ] then # we add openssl 1.1.1 libs needed for distros still using openssl 1.0 cp /opt/openssl-1.1.1/lib/libssl.so.1.1 ./AppDir/usr/bin/ cp /opt/openssl-1.1.1/lib/libcrypto.so.1.1 ./AppDir/usr/bin/ fi # https://github.com/linuxdeploy/linuxdeploy-plugin-appimage linuxdeploy-plugin-appimage --appdir=AppDir # raspberry pi build if [ $(arch) = "armv7l" ]; then rename 's/Rclone_Browser/rclone-browser/' Rclone_Browser* fi # x86 build if [ $(arch) = "i686" ]; then rename 's/Rclone_Browser/rclone-browser/' Rclone_Browser* fi # x86_64 build if [ $(arch) = "x86_64" ]; then rename Rclone_Browser rclone-browser Rclone_Browser* fi cp ./*AppImage "$ROOT"/release/ # clean AppImage temporary folder cd .. rm -rf "$TARGET" RcloneBrowser-1.8.0/scripts/release_macOS.sh000077500000000000000000000034741362250633600210540ustar00rootroot00000000000000#!/bin/sh set -e QTDIR=/usr/local/opt/qt ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"/.. VERSION=$(cat "$ROOT"/VERSION)-$(git rev-parse --short HEAD) BUILD="$ROOT"/build TARGET=rclone-browser-$VERSION-macOS DMG=rclone-browser-$VERSION APP="$TARGET"/"Rclone Browser.app" # clean from previous builds (if for the same version in releases) if [ -d "$BUILD" ]; then rm -rf "$BUILD" fi if [ -d "$ROOT"/release/"$TARGET" ]; then rm -rf "$ROOT"/release/"$TARGET"* fi if [ -f "$ROOT"/release/"$DMG".dmg ]; then rm "$ROOT"/release/"$DMG".dmg fi if [ -d "$ROOT"/release/"Rclone Browser.app" ]; then rm -rf "$ROOT"/release/"Rclone Browser.app" fi mkdir -p "$BUILD" cd "$BUILD" # brew install cmake qt5 cmake .. -DCMAKE_PREFIX_PATH="$QTDIR" -DCMAKE_BUILD_TYPE=Release # brew install coreutils make --jobs=$(nproc --all) cd build "$QTDIR"/bin/macdeployqt rclone-browser.app -executable="rclone-browser.app/Contents/MacOS/rclone-browser" -qmldir=../src/ cd ../.. mkdir -p release cd release mkdir "$TARGET" cp -R "$BUILD"/build/rclone-browser.app "$APP" cp "$ROOT"/README.md "$APP"/Readme.md cp "$ROOT"/CHANGELOG.md "$APP"/Changelog.md cp "$ROOT"/LICENSE "$APP"/License.txt mv "$APP"/Contents/MacOS/rclone-browser "$APP"/Contents/MacOS/"Rclone Browser" sed -i .bak 's/rclone-browser/Rclone Browser/g' "$APP"/Contents/Info.plist rm "$APP"/Contents/*.bak cat >"$APP"/Contents/MacOS/qt.conf <nul copy "%ROOT%\README.md" "%TARGET%\Readme.md" copy "%ROOT%\CHANGELOG.md" "%TARGET%\Changelog.md" copy "%ROOT%\LICENSE" "%TARGET%\License.txt" copy "%BUILD%\RcloneBrowser.exe" "%TARGET%" windeployqt.exe --no-translations --no-angle --no-compiler-runtime --no-svg "%TARGET%\RcloneBrowser.exe" rd /s /q "%TARGET%\imageformats" rem include all MSVCruntime dlls copy "%VCToolsRedistDir%\%ARCH%\Microsoft.VC142.CRT\msvcp140.dll" "%TARGET%\" copy "%VCToolsRedistDir%\%ARCH%\Microsoft.VC142.CRT\vcruntime140*.dll" "%TARGET%\" rem for Windows 32 bits build include relevant openssl libraries if "%ARCH%" == "x86" ( copy "c:\Program Files (x86)\openssl-1.1.1d-win32\libssl-1_1.dll" "%TARGET%\" copy "c:\Program Files (x86)\openssl-1.1.1d-win32\libcrypto-1_1.dll" "%TARGET%\" ) ( echo [Paths] echo Prefix = . echo LibraryExecutables = . echo Plugins = . )>"%TARGET%\qt.conf" rem https://www.7-zip.org/ rem create zip archive of all files "c:\Program Files\7-Zip\7z.exe" a -mx=9 -r -tzip "%TARGET%.zip" "%TARGET%" rem create proper installer rem Inno Setup installer by https://github.com/jrsoftware/issrc rem in case user wants to install both 32bits and 64bits versions we need two AppId rem 64bits ;AppId={{0AF9BF43-8D44-4AFF-AE60-6CECF1BF0D31} rem 32bits ;AppId={{5644ED3A-6028-47C0-9796-29548EF7CEA3} if "%ARCH%" == "x86" ( "c:\Program Files (x86)\Inno Setup 6"\iscc "/dMyAppVersion=%VERSION%" "/dMyAppId={{5644ED3A-6028-47C0-9796-29548EF7CEA3}" "/dMyAppDir=rclone-browser-%VERSION_COMMIT%-win-32-bit" "/dMyAppArch=x86" /O"../release" /F"rclone-browser-%VERSION_COMMIT%-32-bit" rclone-browser-win-installer.iss ) else ( "c:\Program Files (x86)\Inno Setup 6"\iscc "/dMyAppVersion=%VERSION%" "/dMyAppId={{0AF9BF43-8D44-4AFF-AE60-6CECF1BF0D31}" "/dMyAppDir=rclone-browser-%VERSION_COMMIT%-win-64-bit" "/dMyAppArch=x64" /O"../release" /F"rclone-browser-%VERSION_COMMIT%-64-bit" rclone-browser-win-installer.iss ) RcloneBrowser-1.8.0/src/000077500000000000000000000000001362250633600151035ustar00rootroot00000000000000RcloneBrowser-1.8.0/src/CMakeLists.txt000066400000000000000000000116111362250633600176430ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8) if (NOT ${CMAKE_VERSION} VERSION_LESS "3.0.0") cmake_policy(SET CMP0028 NEW) endif() if (NOT ${CMAKE_VERSION} VERSION_LESS "2.8.11") cmake_policy(SET CMP0020 NEW) endif() if(WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX /wd4100 /wd4189") else() add_definitions("-pedantic -Wall -Wextra -Werror -std=c++11") endif() if (APPLE) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9) endif() project(rclone-browser) FIND_PACKAGE(Qt5Core REQUIRED) FIND_PACKAGE(Qt5Gui REQUIRED) FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5Network REQUIRED) if (WIN32) FIND_PACKAGE(Qt5WinExtras REQUIRED) elseif(APPLE) FIND_PACKAGE(Qt5MacExtras REQUIRED) endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) set(UI main_window.ui remote_widget.ui transfer_dialog.ui export_dialog.ui progress_dialog.ui job_widget.ui mount_widget.ui stream_widget.ui preferences_dialog.ui ) set(MOC main_window.h remote_widget.h transfer_dialog.h export_dialog.h progress_dialog.h job_widget.h mount_widget.h stream_widget.h preferences_dialog.h icon_cache.h list_of_job_options.h item_model.h ) set(OTHER pch.h utils.h job_options.h ) set(SOURCE pch.cpp main.cpp main_window.cpp remote_widget.cpp transfer_dialog.cpp export_dialog.cpp progress_dialog.cpp job_widget.cpp mount_widget.cpp stream_widget.cpp preferences_dialog.cpp icon_cache.cpp item_model.cpp utils.cpp job_options.cpp list_of_job_options.cpp ) if(WIN32) set(OTHER ${OTHER} resources.rc) elseif(APPLE) set(OTHER ${OTHER} osx_helper.h) set(SOURCE ${SOURCE} osx_helper.mm) endif() set(QRC resources.qrc) add_definitions(-DRCLONE_BROWSER_VERSION="${RCLONE_BROWSER_VERSION}") qt5_wrap_ui(UI_OUT ${UI}) qt5_wrap_cpp(MOC_OUT ${MOC}) qt5_add_resources(QRC_OUT ${QRC} OPTIONS "-no-compress") source_group("" FILES ${SOURCE} ${MOC} ${UI} ${QRC} ${OTHER}) source_group("Generated" FILES ${MOC_OUT} ${UI_OUT} ${MOC_OUT} ${QRC_OUT}) MACRO(ADD_MSVC_PRECOMPILED_HEADER PrecompiledHeader PrecompiledSource SourcesVar) IF(MSVC) GET_FILENAME_COMPONENT(PrecompiledBasename ${PrecompiledHeader} NAME_WE) SET(PrecompiledBinary "${CMAKE_CURRENT_BINARY_DIR}/${PrecompiledBasename}.pch") SET(Sources ${${SourcesVar}}) SET_SOURCE_FILES_PROPERTIES(${PrecompiledSource} PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\"" OBJECT_OUTPUTS "${PrecompiledBinary}") SET_SOURCE_FILES_PROPERTIES(${Sources} PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledHeader}\" /FI\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\"" OBJECT_DEPENDS "${PrecompiledBinary}") # Add precompiled header to SourcesVar LIST(APPEND ${SourcesVar} ${PrecompiledSource}) ENDIF(MSVC) ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER) ADD_MSVC_PRECOMPILED_HEADER(pch.h pch.cpp SOURCE) ADD_MSVC_PRECOMPILED_HEADER(pch.h pch.cpp MOC_OUT) if(WIN32) add_executable(RcloneBrowser WIN32 ${SOURCE} ${BACKEND} ${OTHER} ${MOC} ${MOC_OUT} ${UI_OUT} ${MOC_OUT} ${QRC_OUT}) target_link_libraries(RcloneBrowser Qt5::Widgets Qt5::Network Qt5::WinExtras) elseif(APPLE) set_source_files_properties(icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") add_executable(rclone-browser MACOSX_BUNDLE ${SOURCE} ${BACKEND} ${OTHER} ${MOC} ${MOC_OUT} ${UI_OUT} ${MOC_OUT} ${QRC_OUT} icon.icns) target_link_libraries(rclone-browser Qt5::Widgets Qt5::Network Qt5::MacExtras ${COCOA_LIB}) set_target_properties(rclone-browser PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist") else() add_executable(rclone-browser ${SOURCE} ${BACKEND} ${OTHER} ${MOC} ${MOC_OUT} ${UI_OUT} ${MOC_OUT} ${QRC_OUT}) target_link_libraries(rclone-browser Qt5::Widgets Qt5::Network) install(TARGETS rclone-browser RUNTIME DESTINATION bin) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../assets/rclone-browser.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "rclone-browser.svg") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../assets/rclone-browser-32x32.png" DESTINATION "share/icons/hicolor/32x32/apps" RENAME "rclone-browser.png") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../assets/rclone-browser-64x64.png" DESTINATION "share/icons/hicolor/64x64/apps" RENAME "rclone-browser.png") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../assets/rclone-browser-128x128.png" DESTINATION "share/icons/hicolor/128x128/apps" RENAME "rclone-browser.png") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../assets/rclone-browser-256x256.png" DESTINATION "share/icons/hicolor/256x256/apps" RENAME "rclone-browser.png") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../assets/rclone-browser-512x512.png" DESTINATION "share/icons/hicolor/512x512/apps" RENAME "rclone-browser.png") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../assets/rclone-browser.desktop" DESTINATION "share/applications") endif() RcloneBrowser-1.8.0/src/Info.plist000066400000000000000000000006521362250633600170560ustar00rootroot00000000000000 NSPrincipalClass NSApplication CFBundleIconFile icon.icns CFBundlePackageType APPL CFBundleExecutable rclone-browser RcloneBrowser-1.8.0/src/export_dialog.cpp000066400000000000000000000060461362250633600204550ustar00rootroot00000000000000#include "export_dialog.h" #include "utils.h" ExportDialog::ExportDialog(const QString &remote, const QDir &path, QWidget *parent) : QDialog(parent) { ui.setupUi(this); resize(0, 0); setWindowTitle("Export files list"); mTarget = remote + ":" + path.path(); QObject::connect(ui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [=]() { ui.rbText->setChecked(true); ui.checkSameFilesystem->setChecked(false); ui.textMinSize->clear(); ui.textMinAge->clear(); ui.textMaxAge->clear(); ui.spinMaxDepth->setValue(0); ui.textExclude->clear(); ui.textExtra->clear(); }); ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)->click(); QObject::connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QObject::connect(ui.fileBrowse, &QToolButton::clicked, this, [=]() { QString file = QFileDialog::getSaveFileName(this, "Choose destination file"); if (!file.isEmpty()) { ui.textFile->setText(QDir::toNativeSeparators(file)); } }); auto settings = GetSettings(); settings->beginGroup("Export"); ReadSettings(settings.get(), this); settings->endGroup(); } ExportDialog::~ExportDialog() { if (result() == QDialog::Accepted) { auto settings = GetSettings(); settings->beginGroup("Export"); WriteSettings(settings.get(), this); settings->remove("textFile"); settings->endGroup(); } } QString ExportDialog::getDestination() const { return ui.textFile->text(); } bool ExportDialog::onlyFilenames() const { return ui.rbText->isChecked(); } QStringList ExportDialog::getOptions() const { QStringList list; list << "lsl"; if (ui.checkSameFilesystem->isChecked()) { list << "--one-file-system"; } if (!ui.textMinSize->text().isEmpty()) { list << "--min-size" << ui.textMinSize->text(); } if (!ui.textMinAge->text().isEmpty()) { list << "--min-age" << ui.textMinAge->text(); } if (!ui.textMaxAge->text().isEmpty()) { list << "--max-age" << ui.textMaxAge->text(); } if (ui.spinMaxDepth->value() != 0) { list << "--max-depth" << ui.spinMaxDepth->text(); } QString excluded = ui.textExclude->toPlainText().trimmed(); if (!excluded.isEmpty()) { for (auto line : excluded.split('\n')) { list << "--exclude" << line; } } QString extra = ui.textExtra->text().trimmed(); if (!extra.isEmpty()) { for (auto arg : extra.split(' ')) { list << arg; } } list << mTarget; return list; } void ExportDialog::done(int r) { if (r == QDialog::Accepted) { if (ui.textFile->text().isEmpty()) { QMessageBox::warning(this, "Warning", "Please enter destination filename!"); return; } } QDialog::done(r); } RcloneBrowser-1.8.0/src/export_dialog.h000066400000000000000000000006461362250633600201220ustar00rootroot00000000000000#pragma once #include "pch.h" #include "ui_export_dialog.h" class ExportDialog : public QDialog { Q_OBJECT public: ExportDialog(const QString &remote, const QDir &path, QWidget *parent = nullptr); ~ExportDialog(); QString getDestination() const; bool onlyFilenames() const; QStringList getOptions() const; private: Ui::ExportDialog ui; QString mTarget; void done(int r) override; }; RcloneBrowser-1.8.0/src/export_dialog.ui000066400000000000000000000205421362250633600203050ustar00rootroot00000000000000 ExportDialog 0 0 614 358 0 0 QLayout::SetFixedSize QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults 0 0 File: textFile ... 0 Settings Format 0 0 0 Text (only filenames) true 0 0 CSV (with size and datetime) Settings Extra arguments: Maximum age (s or ms|s|m|h|d|w|M|y suffix) textMaxAge 0 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 999999 Minimum age (s or ms|s|m|h|d|w|M|y suffix) textMinAge 0 0 0 0 Minimum size (KiB or b|k|M|G suffix) textMinSize 0 0 Maximum depth spinMaxDepth Don't cross filesystem boundaries Exclude QPlainTextEdit::NoWrap textFile fileBrowse tabWidget rbText rbCSV textMinSize textMinAge textMaxAge spinMaxDepth checkSameFilesystem textExtra textExclude RcloneBrowser-1.8.0/src/icon.icns000066400000000000000000002457221362250633600167250ustar00rootroot00000000000000icnsKit32unonopopnpsmpoppoppoopoqnkUrqpoppqsnoopoopoqqpqUoopqUspooUqopooUioopopiqpnnpopnpmpopqprsopoluopwoopopopopmpnoopqopoqUpopopoppopoqp oontjmmjtpopoporporrooporpopmopopqmopooopmmpqopnoponopqpoqopoqmopmjpomqopmopoppUpoopqoopqopompqkpporsoppopooopoopopomropopnopoposrpnpopopqrporlflpqopoppmmmpoqqopfpopo*8?A@@?>=?>Dopq :A??>>??>>?>?>=Upo?@?>?>?oopf>?>po=>>?=rpo>?3popq6?>?>sposA>>?Bp3?>?pon<>>?>>mopop:?3opoU=?>kopA?=npooB>?>mpon:>?3ops>>?@mpop?>?qopow>>?opoo?>?kop>?>Coopo9>?>>nopo?>Hpom?>??>?>?>pop?>?>B?EE3?opu?>?@6ropn>>?>>HqopU?>Bmop?8p?>?Uopt>?:npoppC>?Akplpsf=??>?<>?>>>?A>?>H>?>>?>?>@>?>=>?@>?@?=>?>̳?>D?@>?>>>?<̙A?E>?>>?>A=>??>?>?>?=@>?>???>>@<*?=>???>?>?>>??>?>=?>>??>?H?>H>?8A?>=?>?>A6?*?>?>>@?>=>>?>???>?H>>?>?H?>?>?>?>>̗>>?>=A>?>>:>?>?>>@*̲*>?>>?CE>?>?H?>>?>DC@>?>>?>??>??8A@??>>?>?>?>>?>63?=<8?? ̪ʅ ͪɏ̀ɘɿɜɟʪͧɧȪɩɫȱɲȶɷȾɹ̼ɻȼȺɽǹɽɸɿȶɟɟ˴ əɘ̿ɗ̳˘ȍǘɲȖɿ˖ȱɕɓɔ̱ʕɕʰɔɗɔƯɔȗɓ̯˓ʙ̔ȯɔʮțɓƓϛȓƭɔʝ̭ɒɝɓǭǒɝɓɭɓȝɓ̭ȇɂ͝ɓʭ̆ʃ̝ȓɭڭȒȭ̱ɑʵwxyzyxxyxyxxwwɒƱ uwxxyyxxyxyxxyxyyϮyxɒ̬vzxyʪzxxy{ɒ|xxyߡyxy͕ݞxyxyfxyxzɕژxxyxvɔuxyxfɔɪ{xywɕ{yz˕yxy̖uxyɕڌxxyyxyxƗ|xyxɔ߈xyxɖڇyxyxɔsxyxxɕʀ߅yxymԓvyxyyxyxyxyw̑ɀxxyxyvzssɑߖrxyxyyɏxxyxzmɎUxyxxyɍɂxyxyqɄxyUɅxyuʋɇߕxxy{͆̈zxywyxxڔxyzxyx|yxm߉ߡyxyxyxyxzyxzxyxyyxwxyyx̔xywxyxxyzxyxx̙|xyxsxyxy|wxyxtԓrxyyxwxyxxyyxuxyxyyxyyxԅ߂xy xyywxyxxyxyzߚ xyxyxymڄxyzmyxxyxyxxyx|yyxyzxy{xyxyzԐmxymxyxyxxyxx̗xxywvyxxuxyxݟwyxyxyzxyxyxyxڥszyxywt݀mzyxyyzwڮxyxxyxyvq{yxyxyyxyyxyxvrtzy   󭬔is32spsqpoppopqpomopo fpqptropof sf`[D3kppo[<>?==?>ooprsppo>??>!5kopo>??>̻noo>??8? ̴? @@?>=Q>?>o?.Łņ˄̃Ɂʂ́ кfȀyxwwxyxɀy xxxyxxjyyxzǁyyxqyvyzwxyyxwxxyx xyyxyh ֶ  ic08I{PNG  IHDR\rfiCCPkCGColorSpaceGenericRGB8U]hU>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ 8eXIfMM*iD"8s@IDATx}TEw * D@`b@e=Ϝ0 ȩaoϿrwӝD D(qNzo,73߼|vg_jBdcƀ6l 1`cƀ6l 1`cƀ6l XԚݲ{u (qmF׶yyD'; ' /Ra9 "CZ,j{.'j`,ܖ ]l!` B(."#h !㻗Bx5xg7J;NpFv%UUe?*EPݰ?mٺ/ZwBF/0y*hg0sg"!JkADQD- ;[ܺ%RDX,U[rκǣcH1g)С=TݟJh8\<"dr tӒL +1j~#glAj![)k+F mה@AY0p_sg#0:a?Bkuu4iW؅eX},Ef6c, xUtg``fR+5`؆Qo q?~$80ǹ!φzG69ԟ[@[``v&] :|1`ImRP_w>>*@Hߵtgrihu7sS!/ey<{Lj3k%lѼGv) 42 ͪ^"8}!*{ٴ^L)@[ ^ʚc)!}{ l"Ui;- S䂝[$+1g5cjBL9{AZ- z᪬8S6ċd{ܡW'w Åv"+%P_AT0p8o[+-@pig`i_;י:w<=m`GA[*N] gex Uyq!l9 mGk 8q(3\%#€L8 "BXB_]Ca_u7a@ 7_u՛f cyPl=±'16kz)"<0{!~"n ԰ȸU™Wʊoo5DN JjY3SP][D1j DFyv単g&a)Uh1]#ԟ}(rd B\ݺ|WFF],ۅOo',OfӪe FUm8'7#W")€tEEA3E`(Ӭ-*:Ok'9^3ُCb:žO#d!;3[4Af~g8\g32#)?ap3~Yy+Qwadz!1z_29)1 F/463Mfu8t>%mҖ-)y/?dK; n;w,d콲@5UW 4j0 dEp^R+n (XQy<ʊo j `k0r:cתs,ú`r\/PN4 vEO lt [e.6$:}eݹ> ̧~.l0ư=x'? WS9ZN{P]}>D-#s E1-@UYzDeZܱ  j<|yrU{擶@]yeרhoDC@cK,U@IJp<i#啽1>j1k"ǻ-9.δϨ|1% IMڜML ʑ4#`b BwmhSD JiU;!m}-wL7ޱ"fZ Xz9+b`2 ӽA`o "', ]X \{犊^TD!C{Ĩ@ X"  $6mye\/}{eXA9iS C (Z]Z@ZcDHq]mX᳜Q ίJ<zD.DA~Ftg{l-#u\_K I{?LfG}w@Y [:&ej Kqc4va{m'ʨm g!13HɅO l t4/|-;c_ ߾@ss)770"PMT޹. 8A:b<A&ᰴ|h?KDW[_)L#D^5/xYiqę'P~#oDoIސLd G Z_c঺>*R;y-u'l;P-:VujSDmY :jzȧ [ #B*KcP`ٖz Y4e@"Ʃ'}l%f,#iJҖa&K 5Bt_0HNjs߉gP 8 ڍRk}xUNDZJ8\uW1=/{G=-@4T0N~R]Bi`-kr)*DewAcKc߀Z8wC ))UDN0x ƊB.V}$Pޚ%4T!cB;2Q(ZwTR\*UǰKžރ4Nxt ]"rp U9^ZHk< < *0^M*Pμ8R"PlYP{[gP6Bn>Ǔg>Chn8)Y޵\@~m"gtZ#E7u2Q&؁]LPt_y3 &G20xM_B!r0?hd  tRCd0A̎2 #|[KgAo"Nt'^p: Se ׂAL/M]`v5XL5  ErHL{aDw c;U#Jg.swTi0  g OOr+OvñbK#ʸruLcձLYb -/쒐(ѻ)zۏQՓӡXT2"IM,J/TV6*F/,!bVH#@ Rse-CaQ()CO bN&уwOhƅ@CXzej};Jj|Gw:([x[=::Tiu.C=\)-a ?4JV#h7f/#G3z[hBV`ڜ}iခ >lĞ(xYH[BC$+J fnTe :]4q`PT,: TxJ] F7ݫsU%U9Px΂Q5.~~MuϰMC\_x-|J.JHwpA_#sx<17&c5umq'qOd/ƒM(N C+?d8U\l2ni^phE`cfI(%Y-[ō׾,c2"gIX3? )d~ь'nYL9guA)+bZ ?jM`S0+ugG"7(~˕%M}5{%S_ +ea0SH#m0upQ&NwfW'4I{=|7bV;tԱSD2g"*Blo#ܴ!|C Uv;W/f$D?7"z?UyZI;0YNT)7QVTh+mʩlruk;̡hfm?SMDkOcn q))}:a=[ i$rfEݧn [+%]p~N6z0QX݉SH#<&*2ԧ6%H8q{ (r`i<eD \mZE=~+)M)f$NEAiJ)&,8py$#3oԧ tԽOAd IX"DԄ3 7pV%u)%69Z]0J]2js梇|e$To /wwJr7ִ5h_=)>*4vI%Zn}b]3oD  0ϱgA3"NAC+!*?w猃Mb;e@4Qr0{B@^x5)Ac/6?hQH:bY{10ThWhOusBW- ?aXAGĻ[|c;5'ag_x5~L/.z J:w< KWC+/02Oi>$A!'%H>HNO$b#y$Dlń/esFo m ~NVB:RLWk:~-օ}:74/-$LߵA{DX`(=I&nI [EOmOF} yCB~=;:y+:)8tuw"&;C;OB >3 f`0}[O$߲#\n)F9{*BaE`6yV^/.; {wݘ N(=ѓW4[2`W p (Px}p7&7rr> xI x+_nChc"(ިHT@^ kmgFhLwB-?K$-쁛o8^B۠;0^,9C@Lҩ̠`~B4PD%|LӘThF%ct梒hߌSbF3&l/L6\Os fYj:¢l  yی)A ;}OOoU/>!VdCSцrЖsv=7s"\Ú;7X@}9nGnרWfoPQͨFs= \FUI {ikPkHH0.,빻 sC[K곀aG6=JNYM'мqPKŭl.hǢYлԅAkN C@s|^ +[e,jLVMb3<y\$(Ѥk!8'*]#YhVѓ!Qw@ "]KSBƫK&rK ў!mq5]>\:VY<{af%{ x;!O>@K_o ^osQ߯3Ӑƞu0v~e]G5m 6ɮ9ҳ9I @#nA%Qcoio|.)gNc7d|eAdIII) B_^F5sǃ‹t: 7 vq0&@*ge ǿ8\ca@atX?їq!VB_5Br*t'ni}VoehY'q Oy8A:UŚ? >SRc;Q-`yQGyi:-LR]ʨXQך뼻f* S`BPyq:{8?)aw-߹Xʜj: gg_BZ Ї z*y RŅ*}Kv? lS9~esC!h3zQa8Zۍč=;\%eDa"._E2C}M%4rw$mV'ͅiZreq'4hPk;\:L@Ye烫 ;upac\5W 2vƍjвQcػk-4!缐*p}- ,?PߧO}O?OׯxnV?}`;/{ c o?%YG[:Ob}sogeup`"kN<q^6l!K<~4nDoaQE,>o}K&_P l'JT?@.NC!nH_u~xVpMLY>P!ЗyFx53q' /%[/Ѩ–As;]J._ o2OZ{Hm5bt\S~j' wGI,6.c;`Pkq7S@m]gP_Ros}itC# (;w50>=~ymI9y Ar]Y6gLL_{u]/ՖڷíePX\0_j &^ŗkwv'aTR0E8la} j/ ~`uT:\9P&/IX5[̀A֓> RnXa~ ǟ!u֛` <2 շ{`3z"$\A} E>,?4Ib(S52p ` koQ;ZԅXɩ@n@@0JNkA~'wSѩp޸j3@6``'Um[qqfh \"Ъ*  6n;22Cu녺 -`Ppxu0(RT- WEah ?$^7ܮWòy h#Hd]㲲kZA@ k"MX݂~Oۧ?a+>#(_KzOڕk.ؠ"8td_l $wފ% ܮ-<0$&|i HbוjWfI 0hS:m #Xa 0oty$<җl7_ujHw"+p(AY;H6#5-=@ "YvsQW)E ڲ7FVK 4 $JyjE'@9ZIZP8ڞpRibkB &۶Y~֙ HHwQyh>WX@I[ˠd03A;糀wa-"BIPZun`(bQ"w/ %hT/<MB4TN+lfN7ȔS!OWjЯNofwCРOJ| 3p߇m% `€1b|x+9J7 {[T2V߫ AthR܀w7{GAH %z ; fE5?iKhwi*Y'| 3B3`zaww2ȓ1t cE))?E':p0֡ t0yķ;V5~_CA0Q-T#&/A[}{'..ArrCU@D1|қgF}4{tΒ[9H^3|Z^=w w&_s0G,P9>3oT {9$= B͞14HxqF`QɈ̄ʟR=5ly9WslXHPADF2qN/URW /c$ y`@"SW ~ hC>-g7ءi#Cġ#S5g#tvnS{3nh$*keϳ'ٿ'=T'of` \w3Ɖ$BGwv 7H!N$ـkv9 ^:qtoq6)@o.@2Fq_.@n3U%SVwF;Bi1?較߳TX<3Cx\%TC~L;qӥJ$otgf#ѥ-KVRZ箽~e啡4iW1 y7{|[U qyO\1 ovLQ7KY`YCba]Ȏ D _.l9_ Eqߓ30k $EfOpM䑧^, &)0|<.Yۍrz1. 0 Gj172ޞC b(ԋ Z`DCGs-A*r=-9%cVo: fWKS/<.x$sA_0HKN/Y؅z)~YRWzn.[-ljMoo7(t8>DB~ ='q]vf7=27, R & Pwxq'崎0Uy(#ÒɽΘzOχ` L_  '`-5P G4 -Rt=O) SXu's` Q'=c s0nZ:t_t]a&Vn/޸qP td@XGj,\7|00s4\U 0Jp8ka;%p\zkh=m `.zsT@ΪE?G~S1 ~1Zp-E‡륝`q##J Hf(͂E!<<\iZk7=`͈:8yD@sk A@0Es{N 0e_)B$!c|깝gvu. g'3Ͳ8ra@,`Gj}Xwg4}iGJ>LAEZ5JO5ģyRz.Ω\VwAMťa= w΂[^`)^rnHߐl = b)#߳S d j{st7Aape|c9,髵uh3KmoE%K{kjtg0$`;kC|JQWGey|C^xA~5 | DPD5#b`+=sC,]IȆ ։xvHdzOniZ9VtX);v0 |ў,f*ҩ:5hG3C5lE!(ĝ81[rW˅bo- `X@@VW <*\X `9 8ռF_F;_ %;i5{ da 4opM HÙ2v^!Juew9]2[H\.yȃ,H\!{3:mfv2v3^]K!@vrQbu {K •8{6\x4CSVbdF]{^A S0?vkR$d:k:ye DpI(\}a@6$/ȪɜN5n?NysOFwv)8!r3c@ A3SA0#5THޟ6MP7,kdU .z߬>ͅj!xpqbҀW2@܎Fӊ]+S?:]yF`>6d~z9&⚄ sf! WA FoYMlB .ǫtA= }:zX'TH{]N80BXu:>Y?~ɭw|?M`hFr>r?!6.dcA5˱;qapcce/n~yX> m )4<0qώZL ~۷ ophD34cf 83K>ZԤ8okm7bΌ#ᴫ ,~&vr1>4Jou=N *YhsƢdCZ?1kI³w BTyAM'vkKRtZJ8xGݻA! mk?Hމ <3KvʃB!߷U{8֭p[ 0o@}_\_@x6u56*I޲KT}?n|Z)wXt mV|NɰWNG k'߃ӢۧRW%2s({ g}{* C^]~KH `cRWQ퐅Ri:l#< m0km>(~:P{9M|}J2Ξ_y MO#s nĞ!(C#M L(X5Mco4w& Kaˠ S`65xn*C/ԑűHWb jAb$20]H;hrhI[:IDATo蔿tYǂb\={f1  !{چ}K\չ>+oe,8N;JzvOi:p_V֣sqAm##2)Bh2Q ! f۸B u&܋]ٖQ @}wו5{2 ZW0Wc>v0W6#&A`p&`?%/$IOFh_B̪U.~6ۋ0]ZGAç|a+ D /\d[Dy 9 fF "X_sqS{CJ ]2q^KF=ǃp(slؾ `I GJ|.p _׬_S鶛Qg'G`=Zj(u8-T,&;~"LvA22dPa4"Ή~=uXy, yx^IjFM c 9BCiC6nCP?P/c핣_` l:2ګ>Z J+ 933|yyzK6ܻj_pp_>'-ID^ՌO*j;`I8fzveT]-P6cZt"Y]+pe/P E>>s!$|e4[erBz_m:;`OmۿsHꐟv·~^#"gqӝ얎\a5,9 -8 繼 =!z-wl^5_VS=?*Iphj.8ݬ=eB#_@6 tFO[&Wymm 1`cƀ6l 1`cƀ6l SdfGIENDB`il32qsoosqppopqoopooqmpmfopofnpopopoooopoppopoopopoopUpotnpUopqpolXFABA=?3upo?:==>?>=p=nopn?>?>3spo3>? >??>?>popo>>? >?=@@?>Uopm?pooppm>?>< sooi??>?>?>>?<=>?>>=>>QU?<=?>@A?=K?>?=[H>?>>?>>?>?U;><ƕɁʑɇƎɇȌɋ̋ɋˊσʂɄωɄʉɂɃ˂ɈɂɃǂɪˀԂɿ~|xxfĂɿ uxwxxyxxyxxɁ߄yxyɁʀ{xyzրxxyxɂfxyxɂ̄yxyywwUɁȅyvxyxy xyyxyxyw߃xyxx{xyxyxzxUxyxyyw΅{xyxzwyxyxyxmxyyxxyy߇Uwyxzwx ϸ  s8mk__66"#VV .1 8NG_r 禖MC7I x5CGNB@@NwO&xihy&t8mk@ 0CR^bd^TC2@pĠsCX՟Z<ّ> JK >? QP$%34<=:=/1! w{BFw}-/4µ6`3  2^&܀(((q*)sig:QP<yWSz sp # #J:7KtuA>  [V )94+0*&2352ٴuYB81+'&&&&&&!!4-ާk=*/I"B' 3?GPUUVVVVVWWWXXXYYQi /YkC$ )uf2:b &% f87Ԏ)mVcm36au4\c)"?N!1K"lJ"V#c .{;:_T /S%$UI)l;V0 VNL~Uqrɿ12_slƒE)  )p?CNIa@R+"x Dxr#Ux[ ,V"""""""""!"2dV8\db\';[YYYYYYYYYYY")wRt\ANG q:6]'2;4O|?@c/ Z[I ơ7z0{~}-z~M&S'\L}* A < }'? BE(' Bq&@7(Uc!:܂;(kRi' 4LƌiT@9EY2⯀YD8;GaQ 7LMP 3k_6p' x*,RW0K!~Rj pyuT yE j9nJ> uP+JK. n}!s1KM1JefJ IddL 4~֕IGɂ5?zPN޶~A ?hrJ) (Kq̽jD!     ic09PNG  IHDRxiCCPkCGColorSpaceGenericRGB8U]hU>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ 8eXIfMM*i(0@IDATx9sW@PDA,ĂjAD15j)X!KMld K4"j1VE ̜oЅmޝ>{9y;S#                                                       ܒ35 @\\$?)*j(*DKD:Qf& i %Tֺ\㢔PkaH)$.{Bhaj![MMBUX:iiY/E(0d|Yd&H< @dϊ)Is5KtbCgJ[V-2k)wB$ 4ЈW-˄%^ pBJ4(THaD&d{3mxx|SiãHKFhC]7TU7A<ӓwxǚH{xOTSe E=;ȸju}4;3XB:150kV{ʔ+0*SX*@ xGG@eVhg4{z-=Нؗ@MT:F85x kfX! 2kP]PA$:^kJ Jh! POϠ߈5s/ 8T[% wm/iSVl|~rO n X.[n<=p ^,,l$БZm uI;?HS1?L$"Kpa\3 lEV8'puer7mHMBW=IpU? &6ANy]nllPі?N2r&W ̍H$ Ն1Adeo | x"]] Ax tG@wdx>/o6D˟;v>33+)jYLoē?WQUz$ >|NA%`-5n3h~Pkg6/3|YR?+_c@x5^īim7e{Ņ4g?/uCH6` 3tR. X3^l/@2.k)1c>zlb1e%%v ;558'mbWXMg)b!8ZmCiwWTI~B^~ա4J@0(MffRmAz L{a/a`Ǔ ąbaz=*;O@)z 𕄙gH:Dwj^ׅ#]'rJaJNa%@ 55Zش|*#y H Ͻ*ᷳ$B]GWU&"4y%fT810MZX/5,,}'z,Hr$@A؈kqU1+njUxN_0DE]r$@>z=r{تE>i@! @ 䗊ӞoKg?t\ G ǖw i߸Ljϐ9Ig{¼ɎPtZ1~e" CpzP6 @`y$DɌtIJb(ZMf㷴Зf'sD Rm@i/5!,XnU GhQU>Hy,$D1bu"4D-JL,H (5IAu .2lQ#T% ݞx;[2K +:a9'F0C"̯8bY @ؓ0VҌA&sƋ_J?`CuH Z\㰦(ev-;[:Q8^8 $(B:wl;Ujy@=@{Wj Q/pKj1W?,sK >:#4,3'?5,H _T酁UNC#8J<KԐZ M>JNetnb#Չ~R64 A[}Q;\],9 THRa @j]d?"@E =,qUB.Ut{bj:w-՗qwR)otORCEa!gq%gE_wt0%\rBࡨ1*I!@\ް)/t"D&s)DrS ae׼ ä7u͏*lOUQY'&=#@3jԣJ (E@Ra+ \Ֆt<%gaϱ!R N@5L ׃ pO]ּz=WE(H \Zˋ.-\S\ȅV*i!S  P(Pp Ky0|| P8eu\=*^y:ݳ ݙ=5 M(zr].<:3^?]*@$GFڰ5:[7SB] }P荡$@\ !e6+C%@QVj5MT5$@! LCC Ѝ)|eWF,B$3(-/^">ׅ8IZB~g5(H гxh8"$:%:!,}>D)3Mm"CZA^ÿ7!g%h_۔F!MFS:@`SWXE&#E6+mxG41bDw[}5nTJZFْV$3FQJRZ,$2")@8(KB=Q.x y, <ڨkS97ga'PWalע xS_)| \R*Δ|:ٚmQ٤~3lgP8#,!Ao^A^Iq챕Kg],FԿi/5EEN )Կ3oBIBy;̕7TľG>\uW\04]]$@E^=/+ql(}{B}nkKrBF%;'3qc0lp 8y9ľR]GX p`XknҒ' %:i`]2c>> a5ks/ųil-2Ɨ^dqXޛw,m⛂=!K_QT{(b"S?0|6F%\^TeRyV(FAeB]5Trpǡ6#ʳ ĕHѵb+⫺=;.ɛl/q`܃)*=۫V1ZG1PwTZ=`onBjB|նxXi}05 ڑC_a5|8q+⌸ù V>֦Te?j}0f\#S.Ű8iRL.8A)f>s$~_S&v@{D?-+uP}0ov V1fiZJgԖ WU1}ݪk 7B%TI33%RBFK 9:JTcĬ20TsT>0 D` bBL"OE" ҁr$,ApI"עN(}9-@;{\s>^:%vEC3{ cϮ\^t6a0ˋ<)LC S'OSX'sWM }\ht'<')gJ`NZ~cCaUB`H& #$&i1uUq3#aTƆaG@8xGjd`l(lDʲy،f_c!;`c05\WJUeFCGQ-+}1HzR-g$+4yo;FY?F)ږ:O}a<K@`FF`"-QGR_?DSs֭XuD-Mkl3x<z4`vĜHedo+ws<Si$2?^E<%.h:0ևj:'{hTȪKmXӛU13QW!JOL9fokf;Mt+K,4[jy\\Xs@x.azƗo{߃A@0ꡓ`ҐNx5xoſ;g"[]‚}!phƘM@LbӰe 7@P{`@uPՋ^riѴVσGDR ɞLx*@P,K)|3ݰii+嫦&KҩZfb`!zltUO:k{hɀ^Z(ɞYyrlYvy۞&z Y@~<]g7_c ρAV,7?t^.I=Kr$^s]NgݺP(b%qsHlsTU@jD_,e9C@[ALo8 zp(DMw:Ѧ~{z@T*+fMmQ5vF2 Mxۯͬ/vS΍x:eJ)* [@ `%x& ˊJtڇYU2o`;uRbESL)dIG^l 88̽ JTh!q1B ZU.`!$P`kPШ X(EM{ nfHNX)e@cQFNt޴(҆\ƜKtrzZ@'\kSk4N(TZÜ<B@@*VC pNSZruhU )p-*$\=h ]a8G/r/ػ}YᎇA `zUgPg40\>#t!%a%KX-/ `ΔX"  HH.͵h:s1=P9CE7 P"Xt hu9&\˦+1ws|vQBEa.Z[f3pv1ĂxpL$gZ4\1}h QҠtWiC4fwľt: -@ @U=b}nJ&̤"Xt S)DWPZ< ωǜs9* 7ה: <۳,ks4j533]|F<*6j$Kt 8Dz,ɱkQ⟽sޖ0^V-Xc߆nvt-Jf;7[BJjSjlqsa%(Ί4Phm 'IIalŎ8jke YD3hƫ:&Az].5h?H?,g(O_#5KJ"Nfk5wl1wQ Β,Aỹ6ܠe+аBÿ]sk#_AgdPb@ c* 6QUq8TtT݂7nG5Ga|!wԯ#UKȷeAfm |{w7 5)5#չZd* #07o[S\#^ǚW2FZ++*oyeB(nej.ֵ  :  rYA"i=O} W+>m&@uKUOދ`p j J^ڞP},-ZH OF~fMou4~jU9Λ=G`n&{?)J߾>$@.@vvK&L!-<@*a %^,_9ob-\"pr] ƽ9`XLҢ"\+1S&K\̢I EԻaLWyR&DyTUUVCv:8VF{l=Irb)^" TUB~ +]XzM}#5"}+&@i’ a)k)mQᖋOp꿃U 5ԑz@ -LZP*]1{]` zBo`-lXmU*yĚ%p8su{Mb^H )iLv Xt'e1ähF~=ߞ-~؂~XOCϿ](W*JD*Ki?(OsX"@.+Q8EwC@7`=EetRf`4?_2!LL`e^Y0=b_bM@g8N%>%s&l֔ }k/Z ,pt_% @}-Q_wHXWrH^| @/r< * k%ŷޟσ#ޡ E)H=/L-`4ϖXi!̧VcieS8qX5c;<"*ŀk.ݸCFȍI?22uOjhe4=5g ^'L#ZK<;h.^&5U}0ޥ4.ƒ\a~Ƹ,\7<сXDnVk+,,RKKRM`rq!ĩ$+-' +al|vȃ| `A8K }){[0Sts^\-%r2u h9jr-Fk5zm;xZnS?eX] pwFR;"?c`9-l;!>o;ign9{asRcfo_"ڻvsG`znHHkS?Z&)O@@p:_V,Rr/bǘ"v3Nbn>12 }(Y GV!p<[rCeݚh砗܁auG{RīQh~1ĺ>y`Xm$zxtG@dI~_\Bw8/]$)`ɵX#efQXC>9L-Va7)8 C``m1\-).Z6wuU>D=mjݷGKy/%nÈm.+ D@ѮDJ7Tifb"TGHY4{|LRh]>y̓{` Eܜ,Le^w:;ī%3AܼqEhmc3'r^*$i%.I$e )G&ݹi$=u5mW1X@ڑl !;L"@c<=fe7 T85 iт3R>bfj[Ӓdmc.*(=:g(x,.?]W<^(PRM;!^ ƏGM2cSix so«)$xUm'cX/늭+k>M7=feBhWP?1ov M5-!~xmM@(Raw_*V\8/PPT>֠XK5]،_u2G1* OH.cLriբchy@4{jTwMݺc1{kXpVЀDd MwaY r(  ܿhuZc66Ě%W.~wC!_s=l_Exx?xLat|  Dӻׁ*/Vq/:摲\"?R?G8VTus&ioNpxOj)m=.6 H3;]s%5Zw #Xޅ+}zrs#K uGV ^W`fWTSpBG]uPgd0p|WxR}Yx5m7ߟ|Vh?a] ύ@$$%A)]R]*/y"5d62A xPoZ`y/nX{cXؖ-RQ4 鳣Lø|'=nX*N@(;A#u 4ސ>yuQCqaL/jG_~C?V%CiG;/ߎn1A OM"T驈l(4=[=ÙQ aln&82~ p蕵C-S݆NF$4xciMxJct>ӗ7 VBNǃe13}=oi-Q< 28N~sByC' M|$UKꧢh6aW7f Uizlklz Dl'K9N:U pjqᘏJocҺe)flƑ(jvܞ%VX(r &޽p]۞:O5L^Y^á<6n)oA\tDep撏i%%UKJ1T{kO]@;͓Ə~$ _4D=vYIc9R^h"<H (9ML1h$N qQTMkk[vFowm`9AYgׁ3]AкNϺO+^XK~iA4ƸGL&:P=bі^i<pUM%~?|*bJ@>XTHXQq{LË>e| ~xUnآ|G2ĺ!X-}uN37:rf}sw\ұBRm_=oF@Tr;R[٣/eD1/VB^xU~)ƿSH<}y_l_/J]^*Asتw:]m{!t9Y&]2L8@K9k:]>9XRu \ <0=,\0p-d{"I'֗2bLe%㗝8G\<1v oY3VEl*Aa: K!Ĝ+(_izwN U]zd艹O0-T.~ΛI Z]RD˪M֨xRm Mp5Z׆B `<, d=`r,r8[`+əwYR h]7?qÔYy г1M,6 Ko:`ps:^oxc\fJ7:G*hgȃH7ZE-Y'&z7 QaX9|m\1/k{`e5~lR3`성Y奁E$SZsΚw}{-/E/<xf߳7<_VU]?d!}]Кgayء~WL+֓@^7lS%=>,Ma9zL }[_վ jMzz_`>rC [eu8GAv11A$#-,xt18up 10GRy7*_%P)CO|o?Ց4q.DBTZhֺq7;em!d㽥 ZR,3˼T:u zkͻftT:QXW߷'tǖg>sRLM]ְ_a.2EcY'q"_1V'G+` ѝ|cOZz'hL97@@^&$VӈR 2'p?Ɩ'hkpӶǁcC2:5+ŹUME^{2غM[l?I(ԛ>a/OufVI˦g b)ݚoN{WJ|W-_29}hhh^ l c^qM"v&^QgW6@m*eWSm Hכ Zw^+RO/g_=tP4Wc}Q RB22m_S>nĻΔūZ:aXI^+vdžlݬ݊7Y-M+|ĺeQ-W|<ج^u2Xep2|xpRX'-%MIyR[}|Xz0 E,j69A SN4<۩N2䂾n0ī`Z_a^ |UͿ=eX(H_=|zm׿'-m-h}YlT0Rf"[/[p0l@~1hxxMNryL@}@m XS[S\⍑+Ǜg1؟I.=Şǃ|#p˨Lwxs@ݟ׽_$<ۦ5EB<<)ιi;r0g@%+ }oYx10M`k)&_UxGc3WI͟"|X'sA>7 1\4i B--{ӟ\0mX|%bT`D`baI}θ7L)+"{]vt'K O}]dN={c\0mh{]K+6Le}Bӡ%;U/uOoz^9O1 /*A{Ω@[2zj=oQw i3b=)^ [U}&}ZYA .)9?WQ4kM`变e%f,ɀYY;]J=!뒙ZO]K=U$#PnF}ĝEd `b0|}C6nY;H8gfKtx <w^5~x=>K$-y#TҒl4Fw([߀<71zc$'\]tvt-2 V,(ɶ`󍀽έ0<}:7M("pPqQ+c+uڨ)CeIC$Pk]SΏa˹cq[2} LK~ /w[)ÆZv}GS.:BzM۫4&p0n8O]QeϘ"C\|>S '+zð3wpK)Kx'xڨ'˒#1tHO3,~ >)A$F;νgʺ(C!x%=.RYRt)&=8ǨKJVmN0ykoK܆Mȸp u~'hUOڗ]؟oqUSlҺ攁l'=9şwC5L,Cء{>]:G^pݖ WӸBHSv`22z?ښ3k ּS4̢!̭p0/O֕.Sū]e9h?g#C%0ʷ%~ZzR\_W$A6il+Je փ).T=. 9 FxmW=}g.@T Α 7a:;z7Y\V9-úʓ䨚"mx}L.|d 7h8 5 Ƃp-ז ;qh$Ǥ_0:Pէ[Ma+}PQ1d`5 ȁ@Q`U29d*i.Q66=u[)/%2yZԄP FzRGC:9G0/<$R DO bwsjuPٺxe C𔀬6iO4'?_=9bV' c|G$p' /~ +bx&pU#%塵:[9?7@ÏX.Lx(HHS*Y'K&O.lda+mx V${)Y$@$IǍPIĵhgގ6jHBvV%] @<%MD D;21iFɮۂjl(WG=r\ߑ1-=q]M$@A U rtRb7D}c# +[ 0O@j O!S@\>}]s`bMm9<:g H uYaxY" @ $`(V1QXѲ>wDR쀓l^п_r5e WW|"=x = QDnP[{Vwz'HH `̔QЫ*HYA{_`$֢m{ן]),HKaٽz[#1BrGu x] ,eއ߁U0z|Ͳ{hAH>g vԋH"pʰ ^ɣӧ͑2%;0ݥ K Sl$چ[K29 tKW۬h/ŻLm& N2Shχ(ICX}1`W?΁ 6!c[{0!yt Dǻddo H:\a\>!^]r2bXn1?tK(~zPɽ*|.h2R0WnkR`P`H$Uڣ R8b &Ѵ6cms.e"u!*叡*x05mUp5"%'.3йe'ϟ-0#|4i; ==_v7FL-xeӓvЖCL)lY wX\'K4&,.ŃǦ2=xxU)ۨ fPoG\0<0"iY)JetLj_i@*G!`5),hHy_W' Z'Za-?eU2U? tuTFkUf ZbjeR8_2K$M2CD&M<# Hv= _VQX J8`4{΃)-.Hěq/;ܲYq\E]k%fb_ȑs n2KlL8)wo"{=nBֲ@z7ElԮ%y'59\jHHiuSux)UVu U,3:uh {.='"Rh_ (47LBq̘-arhWeˍ >l$F:kΞ aωFt<$P== P3 pNn; `H Ÿ( EphuDUN@ :n֡mM({=L؃l z *"pfb8k0xZKMZ:f~^<04VkC$ToN ˶&H 'h@@Np 6=q8үbֶMQdϑxâ3 2dkQp]Fc]ni5e]XHDc!M-NzP%XwLR,U}IY$,:.V+`_6eh!EKOG6/òaJs'rǤ$Б~wt:qi<؎%(܏K-; g) ,58t]ۤ`)EmBE@k:nVY R*d/dpi]({Xw EǢHUXL#%P;--mM@ C[SqC+ JgKeinX\`xld qSNGѷ:M`7DBÉt\,k;.}ąOR ű,ȺV']325;dPh9r߆P ste nPWKz`Zڻ RgEN2C2) Zf&.B tQvQ>bG8X$ %"Ȗ@&Z̀8-z;e JȖYٺz* ?׾ X-n{Jțe U7l3jFLC]}T!ʢO+N~%6Hͷ8N%a 98|- aNhuj\ey`LYrU@" ^vY`l 0fʟ45Sjål"Xx Ԯ%XÞ8kRPz p?ac9~E}),N' ͂XBM]u,R3IL 7<{b&;c%2AzW]8]),=$̓P<Ƀ:H]p73 06q1ɹYҶ+:r?&|C$>~΢u񱘖B@*=g$R|</^M$`f#w)=K - *ubشJD-Fme>U2@->sq,-#zI:n}G(fy)֖؇\" XUm`HnX3xѱ>U\ڠϟb" :dRF,> Rseu1..*Pzc{R#1Hq0@9VW6Gx "z `S~$R~wʶ<G'j_H倫 Y~ "aY@vX 0!*F^+vtEQ EBLrpK-"Rm2[t-Wc?k?ŇHnߝ'7ϣ(ΣͪDm$.1Y%f"UmW('V_HݮMhow mꟗ?j;S ?xJ@ YuORX/dڏ57TT/$Y eGI \$% (,ypT ]cO]'1%鱳(2B/4xr?ZP Dv܅lEMͦᗸF1hdKO_yHx)#"yq{TZjKkC GOw.;D$b!0.2OG_ץpJD Y?SV[/Bw5JrKE?;Iʌ{Y-)/\6y@y!z,q.bq=jom 2pR7nnnc[h}G7y?C%Z3>>y)kRw̵ q;mPwuSJ$J ?;}<)Q/?RWz@w&Ug9n aQ6!$UnVYoV?ҪljLT*X*kaER EeMȚMvsrϽ=ϓs杙ߜygw /m% N%S`ٓi=BC,cc# T|KG%+s)_%._Y\!%w_Vv@GP`貍`0F~Dټ{m1ҷ\etK"ᐂg&os.=\>ACiXؗszuqƔߛ{Oudͥ)qN1ZE< *)սSb>@[O@vD14FbG60Jo&6@MyrqbI\x=ྯ4-ʡ' yK/$B`h t63bf3Zn#68[tC HQȁ z go(pv|t':F;8'ޗ`k8wAG'̈50s3䑌Vl/㩞 HuR4=n@8aD8AK~vq @ݸ3D[f-dlEKlu<$1"6}a࡚~[KqWLM˟41A{b)coIjwAv8beK@p:pt@n_#*i!?ꭕ5AJ@?]R͍#pRIZk`@(/Ώ$<0*mj_8;VW#io$ 8/#N$χ?/%:;1u&Y.:Wú"ۿvochTb;[zj,f(b}4=>`b$KzLP=3_B] M-#LEM MIAB{&1M*.E6e%mO=%e[[(g-E+ -Hl,|LW$QuFCc&wB_M*o?ލR64~h?ztjo[>QE?(|c8Um-]ӜNi:R7Sq'Ψr@FץWTRTXqvjc+*s{:7$+~68;\[=LTg,+pdP ~x}V 퀕 d"jJD+eU> MS4-@Ŵ16wK},\18ǘ'$" 766;oVdw %J>4twF幼q7U8 kvq{J@4 @7@w?up1ڃ;=#2icOK,`RgUʋ E zsBj&;5>3ܗk!0AI=`ɐdMa{q;Ӭ  "ڼd5Ȁ\s [- {"Qt F$*/*m%@tHVܞOtrR5mɏNja@Bh7[JS @a۰syK~p {՟8~,&(ij/M8gzƁtpg /&r5S< ~Ւ4v񨁖S\4~^Ĥ"=b-\W~9N\ygɞD_Zx"?\c`9L 4,D1SQR-\1Uy߅-Ȣۉ%hw(CӞŐϱ*]N'zoOgV|oǚVM?Z)/`*-f!քsj(glhHkS rDmBrY&4}*ga=(֥Bj,r>lȳuRR!Ԭ:oB{2rL[cCsŚ)8Vh8,VŽzhm.Č78]is7=<E̘9B汷QN}X#-*jJLk S@{c,ԭϗzWBׯ2o!Fȩ\0SxrhZȀXS; @]9*{_c 6S[/|3El6 +:4NAt p6u8 A35)r.s0(xɏm&Ԃ\o"VL{Lєkztr% WDTO?g^?S4z 7߸G<6VfnC=1BtT8eSrSnA!`ԍBOi= ePAOO,kSnKHI!Hwa*|ϖOʉ)m.Mr?1'*B3QE|G^Iss5.>n&,: ,|O~GR6^ΡZ ¥Y-c{v:^zݓfř,9A[}Ea/QťȑVc; uE L=x`3 (|Jja+h/l\?hFiw %ě2SU=> Ф2t.R;cwkG_VTZg`Q#}cW$(|ao%֦+.@F-Эy@g@z>/L8܂;ߊ {9bU>1*0V:H>3ǬlSu}HC?WT#]r6^I3Q GHG'=wfR)F|{0GL֏SI#$z $zl]SJzt"ƅ؅OnR.TE %aŨÔ{ 7=bݺQÊ72]#[Ј=i4> {5K+[{=Kg1ߵ"x@|)TRY*`r$ (R1_ UǶ[3eyx9ڦ:_ɘʶ2We&s 1ЫaTUU'B)Eo/:#TH Z7Z5Ԙ~w(t m:u$cA4NUhA)F`Lx|;( GMd"+ >ɀ"j6* n"Vȸ^H7MqaaAIdRޯwa| x&{mŻ m]rU 1ڣoaN6Fȥ(oֿO^^j| x~LCD @)Ykv8. 7*&7m"޴P8"@RX!uΐє$k!M#N# Ū+7,32#2(, DQrVIWf\C %FxOOHrA:¼Mkm.x) OKG<95?Zי_ GER$9UP__$D'wG/6n% +5<|6Wk'Hx?xR '1..f@9 GK-gx8,%xQ4 o;4nC=|a)1" rfe2MQJo_؞'@ lx .W$PP"@vPO]64@$U,]D 5UžcQa6vBN ^4' a?ʏE4@&| xѬđ"p2V46Q_hD%D ^+3 L CHEJ!IzǛ^<Y1 [ƛ G@rYʓf-aP>uV93zrTpPn*54׳ 2]D љUӳ1=i xW{")D/0\#9_D3"">0h؄Vb0zz&V$dD-@'lCoTpD 0×Ŋ\[Ȅf\ wE H4$"!^zzCN4 j1Oc,S=yYHgX4XMk!np‚úAxBpD `q)\naY6KF[)7eEK'uF|yOǨO`)eD  GP}YȄCK\+3A`ssݳb$h"6ξiD%D  2>ou,Wyй/ 'vR9'`x/~q}2fٕnO˿:+2K`1:C$EJ| r2q:,E KRnccP+$J~OFp8~9FѼQ7AaH<&#7)UU{ڑ1Dg]R"}1ݦ4S4D8&{bKnI]ٟAJ@08>"#iӳG}@1d$ ⡂\[v&V@f#Je;3Iq+ p;@ċh$wAdF RX"I]T+*/?,G"ȋ2&##viF@RX"D0#D+?bwmWdNdwS|@HCz>+ʈ K |_/Kҟ@[SF-5O  #/;Tg{BxHa ?㙓&L<4f,v(o:!wFr\F%l= BiH\ 8 9 pڨ O@ gʵcP@YM%~'IE^9cv1n_Uϟu#yD ] ?^v(2P7hm0)<0e3td{f,TqVdNWN5 ]25$$fev7EV pQyGq{e=5I%b\O7 ]%H({{(C\(ɨ#n4,G˒~K K) '%@2]"0\`{`7은)ǿE0FH;QKc $ cgo<#Oy4iAXDKg*,Q\қw5¤D nb&׽w -IH.*.ܢb]D \=zfC*ѨWQY/`GAOT$ dcqau(A3}IyG؅#mp]#4BcI pT)SbRy.FEbѩdUy8==D6vL'FSo0Cv%`H zLCb-);+lULKNv+w@ q9=Eu|ʈfEtpBBuf#Dy%{'Kj@=Tz 2 '~J-K@҅nՔk/,=`.Gv+"PcGj='%WjeĹG0ѹ7$Fv-_}i/<<3q7f2̐I2%>W IDAT@؟=)\Hv)Ȃ8av@jz,?y `vT]\UhSN,U=ZC%KWRPY9&ŤSJL})0$F$O@UL(הOլU #Ǚ%4! cz_M s{J@T*,Kzbsf{ˠ ? y'%T&3EcXjLm-ʒQkt.~7[z@G]G.Wjڴ옹Sqj)]D9zERSR[]2ѯM3Cd"GabWeYE/;^ zx*  A@X:r吞 ה\[X5gө0XO7g{ŔJm'A>"j,sSl}[=Z L;Ӟc+ɵ'H* m䑻J uuP8,$' 섺}_VgVX7WiHyEC0F;BS @}]U٭eff\|N7S."C@2vhOϪI @cXKF~34OB5 FL;[OLga\a~\h\EO@㯪QO_KZԾ aFE"XK>UuvsԑSvFE"A.c :fM9it0^i3jO"4xn6mpmPU QԍjO"J0Bam^H> VdyOS|-`IwjH}^,8 }%& ql1]D `D.ؚ9?iHtyEe.?q^4tƏBޚ[gb.lˎ8)"Zȡn%hK r<6|:`/ w.&1i_hH@Ǩ zf| .RhS@}s!g}!u78"@mIg5K"iҏ'}a_+D0,GaW©ߔ`osG X)|]u(4ĿO['q`й>mV/ZW|Tfvzfd`;#@Ir~)oT/ocLoس[2)fRjm+|g+{?ngv)c(L!h7;X•epo >ʉX׋)oœcX6v09vÓ_j8{kd>|˔ȘřXPr>Ïz32Ev/pS,ll*FHwΟPYey0-B4|~I&hbe˝Lo<894S']5O+ۗ7\N-4~) eOr,8-*D5F {kB. F'rhgM B>@͇Uϧ4J=ri,Z S Qac9i$*N+(0)>G38Km;~Ena9IaOL!`]G`O>,dhMm""?6 )5, z|]l~\`#OۂrXoYrQ Cl>ܕƢJXBIuRCvLe;{dDD8TIt7I,WU~X: xd'k0҇hʼn|uZ3uvLqʒ;t,(j{. :Z&iӏ3#b.*=VNyhbttHPj a'xml[|%[׃}QnIeT(t*Y&7P>w|VFfW)$AcxWǸr:C¥PѠ1EﹴӮQ=@5pol5; <3 3ǶE5:g5aXEs$@ 3PX؉{?"pN (h:b BGJa[.>'#>C˥;ٱ^- '  /!ΔwЧUIZhnDPީxǘA>X#hv"_"&E(_ߡyiF_QN$|P;ʏa mp+GFa!|5(lvgKWR))ZgKkHw:kEC@P3K X4~h $jc>Ѻs!+g;8{$(D%Q۝uдpwSEJxf:;F)BhPnſ$ e >*4l ߗKcW҈@R4iT ˪er۹º#uDe<O p+N4_ !WLa0҃W1z1ek{9V늯$Y[]6 _.ڴv\jWu>6٘U{δ;.߲ă7 zhHfax}P"& F=ÝX5m9̈2LVV*l;8FAmdDC# /2?~gp镟B#A O<do)GBd5ث }K}!<|D]*"HR9Xy{XЂvxw+$fx(ΥwOx3'Ow^+EQ/8:ZƫaEBQ*nzUOA%,ԯt5M=z)L̪صqXH`^=ti13rRgta$ʄpU~̔,n+Uۻp}7/'!Ucz8YAgȵtXӋG D"@ D"@ D"@ D"@ D"@ D"@ D"@ D"@ D"@ D"@ D:?ƖIENDB`l8mk 00 "qڶq" ii ST{{IVVIhgĶ;KS]`^[H  [F= .f/:6SF;$ v$9+EApF??@1boB"*  (Ȗ l[1~/Ԓ&]\$:ׂWbKaV;Jnv/T1$g$J~(&~٢J=NI11JO>RcloneBrowser-1.8.0/src/icon.ico000066400000000000000000000607241362250633600165400ustar00rootroot00000000000000 #V  $@@ ?C  -T ]PNG  IHDR\rf#}IDATx ؖcǟzh! u $"B SPD1S"dR#d*Q$T Ҧ{?}cnWs?s}^;}}K,C:tСC:tСC:䍿~1СC @)P@yPKϔPСCKk#ex\ ځR*:td%3sg/z5x >ʫQ|W@^6AB+oyk1!UtH]t(w+k[$=`%n-yfCsG_*rlyED:"|zk>;xUPV@GԄO|,Or0EE5&y'_)&)Si`QP1@u5#*~&0ZM@G? et3@}Äop5G_:KPW@?,}*N}BwMv!|>/U@op{Ғ_+pYRF[Wُ "6"xuu He_X4p% C]}3BEjOaF++/HEI[3=?i;& :;ˏȿctĞ4K?.j2 Ďu?ۢ (0gr9_߃WͅLr"4~\ ,1asp9w-ʹS4 2ֱ+2*m&!-;tszR?Y\|tq _4Mxn`(@G\'!6O>8 ܬ d?L8\^ws4|L%NR|YxO\ +|LTxhlIv 'o@F9;Q'S,h,M]:l2L:$K}?G}iKܹ^uDg_RwIS,S OR~|e3&Ѐ$GA}kCKw;b\L*`r<@]*P3(3(A @}7dZ^?B|l幦`ل.HMڢ8P7 L{Lwv"p6H DN;Z{c7 4GW:HT%eahhScJػ̤ÎTK+.%mi[fp%W^dS {wt}y=!͔4GgpDM8^RLQ/1mߊxqvL²v~Ǩ_%y J:=U IVX: :Y}Tx0?S ԉRLEzAߟ=,a@? 6sC@i;9`+8XrG>D:®q.E9HqhZȵqip\T  |5{&Й)t&='Ϣ8W@'^/3:rp [╂jL'P#nҟkI:@8$k_7(auu1R}WJT(LR>ـ:f]TuѠ ׂ~`0 GOG}1=< I0 ԓlU[K2#/xyph` ,V{AEƫq&` *}E_y`5 H @s @b1ק\:=k2F fP'b fDGTmbm¯#'`KlB߹ },>}7z$PC \qב?W쉘l[ c`N4^_#!:/|- (?݁㤱 @ {¯f:xW\Uf&`|,gw3"pA_]*6 ?}oZ3#`zZjwxJ<*f#6`}p 8@M u/ٳ`:-6?dK"c`8T#m0*̘bR xm*,77_ m< W٬yyq,]=p(98_582kI5\y*ؖw%:$) 96 (/0O?~_5~D͝7aW*X S@~fPA/i@rPI^RsjP߅ſ:^4!`]@l@ L A4*#`,G9/wC-x -3sY>0N\AW&3 |2 sneX2l0_KgI/wrN?O\БWLG1|Ṉ⦪ $ fAQ&Si|;T+o`> _Y^,c>wU|Vƪ=q`M!@֚ϲL^v  7 ,h T sPn c,?,͑Wg[u;#~:h*=[W+o"h28ꏾķt|p&#Qa1O-qOK  Xq(QM?~Qg@?Ʃn+剉f]|,y /"\3.Z_Lͣ=\}RM箿 j#Sܡ|$s]?+0 fgߖ{aWQZ|:_Zvϰ@2crv`L` :&s\Zd?7I} n!վ!oJd8/~&zEAW Bl*:fpC9O/bq~l=C o>VBy-rNO~@,ʺ~Tfe\^=p#_I~ǽy˄G9G!{&`JfsPxDrȵ__oȯⰖ3chlXX `* J\Ht+pPM'ZJB\3Q׃I.䊽 b!,)Vk/o{#qC[$UYf`?0Bf%8&#_@~Z$~;/#0 gb!Zwc8}VҵݜÐ_ 7*mU\rج,}'cHԵwpy*kA , Z\L} ?C܀>4߇ _' ˉ46kp:Xǩ OѯepcN5PWF%3D(wo6ZnTz+Gg|}fDL7ӹQ-ͭXTskK8%ǧ 0 }lE\ͨE> h YjOrpeno\@*_C %_53UŮtՁOA3S8/B5)s\dTF 4,+}+w, 2c9s!Ӕ 5C>hc2|xqP"A_]Q[fЌ ?T &[ ~N3} Ncn!XJ FU97<j@Upc[ۼ sDk PY>c(UdObuQ+j1ƞ?_L*@ c `($0 2,7~<:rn|K/M X64 `I2qUy5j5;ǽz{k Ln[_W n/M1TV\ ^V P\uGrpS?XxGܢ\o T9"eC&@6ޠ o#ſxTe5\ls8~d@{\ grtw5ude+S9 (Ņ7|mcޢ? 5_& 2L6I-r3iWj:>[ PrPwCGM& ]I1N+Ptcq_GMRUDך17Ptjm )]ip 4{;sG`1JCq'׿>,)&i!0c!\6 39nľ'Qy z?TNm7ov3Z,׿ x9Kjog7[υ[_zc@y5Ӆq+I_o?ի㉱ֻjL"%z|9.~:I|ť&;۟$O"R`|)o{a`) :.~jX'@Ÿ$#"N`u'^p^ ޮK`keIjqSMJHVH] 21V裻kwe9o[)5lic‚(b, X6Q6njIa;Al%)5uIӹ @wx((@ ŵloo95}aWa~9Y-`W ,0 j nv 3m3Ē?]r}}l$X s3&NMA&|5}#mJD0W *I\Q &+rQ63(Ôl%]aPiNP[uws{Q5]v@\ 3)τ '&`֮fBbY0F@<E|qhs.x`(x` \! zs6yTfRJbW //t+7#T2[ tl @Q)fqpwFb|~@ ^K2cPE xe DA78qtmG@Wh +|=3_+KK{WտN3R*`.Y\>xoS\4@/4B{* o\ wnK\0e9ˮH(LL1Za}ơRѳ|~ j"x@~v) ǯ#o ]Y&7M)翇 3}Oh l0,Y7@ ;9pm; c%PPĭI9i:i\9l5.|L`#1C(u j[ܤ( 0+s.0X @ԙbuY@ U!})F00 g@&i3+ g ^~3^ O<= /Z5+gBނ{®6:1:4Spaf@L0v>Gc¶7b wN)RP|ţKԝi8d=ϡaɌ`ЁUDf!op՚< @}gq\/9b/YZ\s$tP7`@c#0YSY\ ʚ ʼh &%EZz7 2W`op5bwWps|и2 K ;v-n =k{M>uC`>`dԞf6ԷQ͈@P1]+nf+4`mf\v.;2*b@wG$pyp|.dft ?:w|o4axrHs䀀U{]'u.aeK>[8)hѡaJy<HP>(tyrV(} .*ݜX2qNz{+r+e'0 VyWf͒ AtpP|@snޚt+`-6tOLN4b$uNty&N:1 ks47  =nV!DzH|_NKT9IRn54L"xRIqW*^a9d+Hx:% 7d?M,f#Sxt+БU_i7TC6 P|oz (LZcMj(b3+@$[*rDߣ8oeTE:sIL??Bq\ U|/TCڪ%9h!T4nR&~i@!Z" VSd̀S>MV>|t_}#Xà ^d+@rHxUѽ3˜JPR0B5sc{۫5CBgF q JY/$+L thr#şLZ): Q28HTdv=&@k=4N *BS0k:svgi08c`ûS09p]! zCm~+(40q%Nz=˭m5ګPWAL`?-]B[fA'ğ  Lpp &o>z:zq'Y4*5~*7 ݩ ukH\Vz3ݔƙ~15ۛ7>&PjPNwN/3*l &iAz;8@jTg'sh ?3fxfV3@[V3c!ub*&@gr*贖T0/~׀ğPZi(x.צOJsFxae@q#"Lol֡*]}~&OKv #§RPI]qYY2>_qf ?E##-\k`g L92(}n 6qw޼/X4ZuS+cϽbq~@s!4|NœS9(=~«`6w֕|?{L(ܾ|-.:/U Uy6ܭ#W}bl5[`dX][pqB A\)fį4{6GfY Sulq!<'2Q(qL}oW>ҏ2 =]QQ;HE_qVUQV_oR<_{X,‹2پZKb w1 5Qpyˉ+_PO,PTP98eBoyxS<B&n-rs[ |+ YHxV(&A\9EÄYa޻P-Lب"5c7y{F(K} .x[0&d4otN/g\$6sT$Ћϩ~ȜC?GN=Mk(,E 8q1+._sy|D@ϧD2>^\t/"@U,#|:v`[9^3 ]Ô޶QlPT]\pӓ]ʝ9?iI$f}Wx6@סB(RLβٟ[’IF`m>$PZX>}#Kė޷G Fa\B:RΖܐ|*_.PĂ*ˣZH_ 3ݿw<ە/XfPXHcc݀kK,ѝ]aн|= P'[]P Tza HU@E}" @ T_ BS$2{[(E,ۈ$V9rOT|ՁyY:m=Yl71paS t+hIP^5  5+$*Y(ǟd ȺiŶb ?rXXTM<k=1bvľcw) 8{#׉="c˧-ycPao`+{}ʦ봳C; +'5 Cw/#BD3Kp HÿőFL/ ۂ)cnr$1Xc^# ]vM¿c'ۓx$s v7o^dm&QPT=wGFS\|{O&L'N^C<2X.#\S%9ŖRKvH=bTB!'gĥ_)(&1+-621zX?/DKәh;HF!Z"ѝu^}6qI8&gad8/]3FY;($~YsPD+3.? `OdԿd1Ԇ%!Ed5q;qv6VHц?0`ۅk8XO@u+#338.T {a6Dxk"bs SfQ+2l!ߨa.P siLA c3~- ;T@m >|N?41M}.G6H9# ]2HE[fw9Pl'ӆBĠgJ ҽ'.ƩQPg"?a!+ԑYv"2&%UÉ7HPn-Eg@Z8Lnoty S_ hYT|-pӒt-߆KoF,0kLӾO8g%,WFؘL课S+KmbL|gr]Rm %\9gLPe ' SL>:&Bݯ^xm+(ͮjL]8 1 t!yՏ~CW$[?uf ^#r50t.U8\^e1a>Z;Q0_v/4;l7fYt02:E# R k`8NB r1SR[Dмjz)&] 9~RYBS[[(T{J b@槗?ΟgI&VLO7aW/&cCƨof J L߽~3- ^V q#03!f\:VX&O˥Tٮ~R5*q0(zm 6:Wщ zn>Z_?3'^p:0 =MBL4切НzڌނR{]HLʮK$w2ORU>>E';%-ZN qhwo+p-t .W U8sBA'A{P.;y eN萤* ?$V$ WΟ}! wD>OIVw4/X0ѧ%[]'ގhV XnF]A-?|A4[pV^sΊuK&qbr qu@ Nw$ Li ;Suc\oȬsP%:H3mTIS |Ll{xV&CjQI~*}@\C%/tSf 8[&y^ߘbd{iy{hxN"R2Gx^;.ߍ8goQ+/Nbng 3.gȜ0a?1n#`I|YQ}TJuDDf͂n4#.By$I\b^W3Vn\ .h921A[LG8UÉ˷_ZqE8h߿J A}]Ĉv`s^dFTQkQ,@.!> kb{kp%yϖVLkX"|u WG*C@2#e'/+0f2Ga2Y1֨c\ y|Kj*A'̢|{^ܓo,3&1e&LY%4<_ XL~AD`xq3M}zYDQ|)c 8×؁W';KQѵZcI'wo)Ot/(NHK\gֱag] %:I̵4/; _NT4Bw~&ܮ1vLLH=ktc Vѷ?e+ ([>@%bSrAoI[Z@Q҇CN^%6Yja;?Qŕk*nFcPXlcy)H]ćPw'Ӿ/~*7^(ܟ0  @N<zп&@_ó6=LQ. >(v 'Q[~g_ZĈ 2cǎE*%‡x9 t+_ay׽` L]$$6~ nTc Α*|) ;"B>r P.ˈ{NOKrMA[!_X^#+LJ- *\]^w=:lNg1.~݁w=#nߣrfZa\vӮZ8 =|4%@g.(7"8!ͥ&.ef]*{#J ZK>N . ~^v&Ue~ϙeƘ(p98d^< )Ϫ'2if fߔm*U?}l7Nwf0/12zd\E6ěƅAy7冃2 )&klߎu,WhLɷSXN'R(;x-bs#x{ ,谴s#2^-^FTF>j6TzQsCvBBI:6|pډ,];0l>qqipDYz̙+zнLj܎j)GID9yʠҝSDrɭNDI/)LG#,4ͭv 5/ha dJeܒXk4a>CG΢) cf =f8#S, 5`\s(o˷Nk[JT "+cU7A&$*9qu&` ´ApJNlzAQ:09BMZPQ?ݲMwg6đHco@V_*K/5B OId}GAhBpo&:hhb#Y/zJ&k7'Z&w,gL"p־ӓx}p2\Gxs0py UW|ij*02bjGht8@/l!z,m$n+3L$)BQ7~ ŷVӯq7Mn(Rj")a;V:8D+-b}-p>8~v.{m9o`yC kUMH?A|Re?anJ]w9#j2NՈĉ߹woR2 տdsb)0KV@8Xni#EC#nK=cHqHi4ekldAvr7 K"Pݴ>xe:vG22 Qy BAHvOk5nenftAiY2pywz ՈOkM 13>Jd˱ݎ -TmD 5e ħm1/>=So$ŇNMIM6ĸD78;"AneW|>B9*sEq*lW*qz]L {l^OFbCU[<CZ7J0V!+(t]6G蒼7&`Ľ]my;=wb '`c\ngt2#!tuÔiğFC FoUE<֘7D84]8iw*lA*ZBsW G @K ]*1ahw~׻/y5g耿1Rl7n#"K[.1'%4sp ڂ@x(ƔNmώF  0J!,2Vq9 sV{AP˴"%.ܪDIENDB`PNG  IHDR@@IDATxyX BUHݰRTJ#ujz[- jP("%T@E0!@"z3fR}&dηezxũSPqW>C183SZliCQ4 x0s˝lό6(3yM#qmߩXNi? KŒyⲭy|_bEh/DxO^ 0t"/TX(i)qso9651w\K^MNm*$3^6B\BÉvV=bzs*b-ߖA`-QOTqCg%Uo_Jאּz¶CQl[ݗr4f`]wl,~5,?3򺒹X,Ro}mk'p-NXs CCKr]'F DGnV~2 "p{d0aEк7CNogmU ȳȚX"LVÆ"&; |p?ng\*iu KyKD.pq# MOVoH,eV(S=>.;PVef#![B\_&9sr&@,Uَ c))7# KSrd-LD&p21Sɞ Eȸ6#?aKcG#jzS W ^֏Xzm&Xa+5XCz*,ʓ * WlRI8B30LSc+4ܞtگֳ*\Obf_,rf<3/wK  [_`K+ju f;!ńaKeT#-@|[3 Chn^͚r"K Ǹ9V^ @Q7i|ƩƘ =k6M#t0aVefF=0RcrdlL!p9#T=n<4^H8j (=@VP=~rpP_?#՚T5c'VPkV.o5+T~F5dT ~oO-#<(m*qポƕ+MǞ\jT8ɰyB1P8ASR46h޶ݩ:C)ɸ旎Sp}{;0h%MH߷ V g!(kkQH`!6Vt3ٵ,qtE-Ki,5jB: _zn 00#3s!qiՑl3D`O_8V([\(Ur*ND݄+&ƔhD|MoZ&fscFӵ x/q_Eʞ1a3!\3- nfRSf¯ <sp3`ݺ{}96p{!ɓϷaR\7歗nq>NHFT/>S d|KK`@|z{|{&+ұ4_Ǵ)<Цp qqMKQIlZPMەyTBh^j߇$`=3{ ffV8 Nx/Oƺ37T 3ef)Ca _eaL|`Fj7v{} r wQN ȡu@b Bz&2\?ny98ak0<3<<^(HRC@'2|+!Q8@wlyR@TFa$u2l0͟zØױj'z&P}(a(L)3U= + oi%3c;`x152 9} B/l`D?Bݣ9sF6V6ejs㨞zJoJdR.Լ[BJ§kIp:Կ1MQ_4@OQ)T#T>5e,CNR*+\޺vhM|؎ G0ߛR,m-c__9hXđ;kD!gwսDgcѝiJÝpn>#hJbRѨ p4ٙK]Ғ0ʱ.! ԛ20,ao(:Z ) ݁!\7r`C>+&QS4)O;pfpnOx!ML4:n^\_sg] Τp:QA:x"m|`J>{W N8jm?ct8.-'_7<n1Cuu a^bnv`f1G@>+ί7p8{cs fHC*=wrc؟E2ߙAL5j~>+/R5rT n><;kD%ldgDb;g卪p4VᆙZ G&mG@NoHww '8֢G8;숙he#SE:R-f2% Da^'"\asJOf«z#ɕ`9oQ#z{n;:<+hg%fWv۵,vF2e}` -j'` #I"з\t:p0hM^8Z7vo'm~~rW1]ًmp4O 7Ř YEunl!B >{t{<`Tk16C:5.:5BPbͺH -~O=B|Ol-v3lA%4T衬`.vVl i9t%{>02zG0`f/_ Sj7EU_ԤIY-)ݧtg©vaG~PѸ71[K!<d!{Skbrgߘ+ؙ$G9-z5 n^mJ!yr_zwyza1.aQkI|:f0^"UMKL0ugwЗ ؏M'`L|h|A` -##m )6\iyn;1/XÞ/1>γxd`g`d3-2_`SIENDB`PNG  IHDR #ꦷIDAThŘ{\X!L4jw޻lLE^Vrp"#2a T({_PcȰ;(cqe"_f`3oQ :IK-mf \߿ |zzA$LgQ<3du?U+݊ek@jn{*v#1>|To ?l3R2H|NZ?q̸Q8d[ZPj_'XܖM'v,0CC|1oQB Ii4FX.XfC6(OƼo*jstc7|y'__ᴣ-eu79'mn]cOz >.N'T撵0a$kf$[O(J+5N8]5 BD.'F! cFz P澤`%?mK˙>V}*["& [ rU˂w>[EJTH񚫀F,p6k~is A"ZѳZ%E=Ÿ71`wP/+y~% vH׸n6PzB.gs Յn D!RwŔ|(uj|PGZ[}=yV=Ei;Ƥ*;, Xw0ިQP 8{h2kmlVQK*㰳,.Ro>5`OZFdA0Q˒z9f>CTM4A@6$#@TG*+i+U92.|xJí?HeME+i5὚H.AܿˑaL#Y-7']BBک2@/qV'J֯=Hd5 U\xLg'x**BQr,UI~M)' 7޴;^I[Ҁ@Ui{S?@ 68UDfԗHcFmIW@`a74ktqg@4:WIʿ#Ϗ!eӉ W@Z$֖nJߕ~LD5WGd̀9@vU.)c"qnfIȻLOf/%OPU"4r5Jnt^A} c7Tе8́tr1<[D~C7NEa۬e Xֱ&f]ߗ'p^d^ntHPfv]jHzܛ ĄbIcP~Ky<=w5x`qq-@zSoO>[#s ޴VnTAS #z#H8aS6}aa/0_G&Z_ qȱ"|SCQ%Eߧ8`K6=Z,̖enUb lV7FKY_Kmh~1pZ>."=ۨs łq[wg.gT|_ά~ԨeKs-ђ'3faz`qTZ(*:Jnvݹn-1}jv>~V,@1;(CXIwA,PG+ !K+kDUu}60NCr2 9xb46LC~ ^$Ne+OMdXVm+׫n=^S12+@Z\.8bG0G2 Gk}yҲhϼQAb$I&zV0'Eߓo/n2J;ht Klp^gmZ_[@VL( ]q_W\$^'=kV]7kz;=kz*XՅ[ 9 D :Epӱ$ p)s+|osgϏ 8b!QhG E҂a}?:lHa3?L\s^$eorv=SR<\Ma \"6|$zq┻#Jl xԀ~(Irؔbʇ=G}rn$$CgV`]Ss^K+&UX|nW4`%C.g \Ѥ≐ Cpcw֝R7 M"5bl׷{- /eb$4^偅2o$/)k_dHR!Ff\~3Li/7WFa쌻X(޳N6FM<%iR) ba34~`~ts,gƩsLI~Rs$*5A n=.Uhu"3i w</0otcQ-o MKA$n MGGURh"rd22f1P͞ЮC KO3"hg`Fڃѿ@&pUяu){*è#lD(IENDB`RcloneBrowser-1.8.0/src/icon.png000066400000000000000000000177421362250633600165540ustar00rootroot00000000000000PNG  IHDRIDATxy|LW' ],v^kjWVQZkKi*X jI$AeO7;ܙIL&̜s̽ysU%.qK\._K\sAG,dC@}*WG+#≃}}bE5DB1K\/)Eb[gV;gC33+icxʉ82PW ([eo')uׅAĝf* Vjy".qY;ܹ3x7Lj7O`.LA)SDl3ϡM\Ae Lm*jSua dX][pqB A\)fį4{6GfY Sulq!<'2Q(qL}oW>ҏ2 =]QQ;HE_qVUQV_oR<_{X,‹2پZKb w1 5Qpyˉ+_PO,PTP98eBoyxS<B&n-rs[ |+ YHxV(&A\9EÄYa޻P-Lب"5c7y{F(K} .x[0&d4otN/g\$6sT$Ћϩ~ȜC?GN=Mk(,E 8q1+._sy|D@ϧD2>^\t/"@U,#|:v`[9^3 ]Ô޶QlPT]\pӓ]ʝ9?iI$f}Wx6@סB(RLβٟ[’IF`m>$PZX>}#Kė޷G Fa\B:RΖܐ|*_.PĂ*ˣZH_ 3ݿw<ە/XfPXHcc݀kK,ѝ]aн|= P'[]P Tza HU@E}" @ T_ BS$2{[(E,ۈ$V9rOT|ՁyY:m=Yl71paS t+hIP^5  5+$*Y(ǟd ȺiŶb ?rXXTM<k=1bvľcw) 8{#׉="c˧-ycPao`+{}ʦ봳C; +'5 Cw/#BD3Kp HÿőFL/ ۂ)cnr$1Xc^# ]vM¿c'ۓx$s v7o^dm&QPT=wGFS\|{O&L'N^C<2X.#\S%9ŖRKvH=bTB!'gĥ_)(&1+-621zX?/DKәh;HF!Z"ѝu^}6qI8&gad8/]3FY;($~YsPD+3.? `OdԿd1Ԇ%!Ed5q;qv6VHц?0`ۅk8XO@u+#338.T {a6Dxk"bs SfQ+2l!ߨa.P siLA c3~- ;T@m >|N?41M}.G6H9# ]2HE[fw9Pl'ӆBĠgJ ҽ'.ƩQPg"?a!+ԑYv"2&%UÉ7HPn-Eg@Z8Lnoty S_ hYT|-pӒt-߆KoF,0kLӾO8g%,WFؘL课S+KmbL|gr]Rm %\9gLPe ' SL>:&Bݯ^xm+(ͮjL]8 1 t!yՏ~CW$[?uf ^#r50t.U8\^e1a>Z;Q0_v/4;l7fYt02:E# R k`8NB r1SR[Dмjz)&] 9~RYBS[[(T{J b@槗?ΟgI&VLO7aW/&cCƨof J L߽~3- ^V q#03!f\:VX&O˥Tٮ~R5*q0(zm 6:Wщ zn>Z_?3'^p:0 =MBL4切НzڌނR{]HLʮK$w2ORU>>E';%-ZN qhwo+p-t .W U8sBA'A{P.;y eN萤* ?$V$ WΟ}! wD>OIVw4/X0ѧ%[]'ގhV XnF]A-?|A4[pV^sΊuK&qbr qu@ Nw$ Li ;Suc\oȬsP%:H3mTIS |Ll{xV&CjQI~*}@\C%/tSf 8[&y^ߘbd{iy{hxN"R2Gx^;.ߍ8goQ+/Nbng 3.gȜ0a?1n#`I|YQ}TJuDDf͂n4#.By$I\b^W3Vn\ .h921A[LG8UÉ˷_ZqE8h߿J A}]Ĉv`s^dFTQkQ,@.!> kb{kp%yϖVLkX"|u WG*C@2#e'/+0f2Ga2Y1֨c\ y|Kj*A'̢|{^ܓo,3&1e&LY%4<_ XL~AD`xq3M}zYDQ|)c 8×؁W';KQѵZcI'wo)Ot/(NHK\gֱag] %:I̵4/; _NT4Bw~&ܮ1vLLH=ktc Vѷ?e+ ([>@%bSrAoI[Z@Q҇CN^%6Yja;?Qŕk*nFcPXlcy)H]ćPw'Ӿ/~*7^(ܟ0  @N<zп&@_ó6=LQ. >(v 'Q[~g_ZĈ 2cǎE*%‡x9 t+_ay׽` L]$$6~ nTc Α*|) ;"B>r P.ˈ{NOKrMA[!_X^#+LJ- *\]^w=:lNg1.~݁w=#nߣrfZa\vӮZ8 =|4%@g.(7"8!ͥ&.ef]*{#J ZK>N . ~^v&Ue~ϙeƘ(p98d^< )Ϫ'2if fߔm*U?}l7Nwf0/12zd\E6ěƅAy7冃2 )&klߎu,WhLɷSXN'R(;x-bs#x{ ,谴s#2^-^FTF>j6TzQsCvBBI:6|pډ,];0l>qqipDYz̙+zнLj܎j)GID9yʠҝSDrɭNDI/)LG#,4ͭv 5/ha dJeܒXk4a>CG΢) cf =f8#S, 5`\s(o˷Nk[JT "+cU7A&$*9qu&` ´ApJNlzAQ:09BMZPQ?ݲMwg6đHco@V_*K/5B OId}GAhBpo&:hhb#Y/zJ&k7'Z&w,gL"p־ӓx}p2\Gxs0py UW|ij*02bjGht8@/l!z,m$n+3L$)BQ7~ ŷVӯq7Mn(Rj")a;V:8D+-b}-p>8~v.{m9o`yC kUMH?A|Re?anJ]w9#j2NՈĉ߹woR2 տdsb)0KV@8Xni#EC#nK=cHqHi4ekldAvr7 K"Pݴ>xe:vG22 Qy BAHvOk5nenftAiY2pywz ՈOkM 13>Jd˱ݎ -TmD 5e ħm1/>=So$ŇNMIM6ĸD78;"AneW|>B9*sEq*lW*qz]L {l^OFbCU[<CZ7J0V!+(t]6G蒼7&`Ľ]my;=wb '`c\ngt2#!tuÔiğFC FoUE<֘7D84]8iw*lA*ZBsW G @K ]*1ahw~׻/y5g耿1Rl7n#"K[.1'%4sp ڂ@x(ƔNmώF  0J!,2Vq9 sV{AP˴"%.ܪDIENDB`RcloneBrowser-1.8.0/src/icon_cache.cpp000066400000000000000000000026611362250633600176670ustar00rootroot00000000000000#include "icon_cache.h" #include "item_model.h" #if defined(Q_OS_MACOS) #include "osx_helper.h" #endif IconCache::IconCache(QObject *parent) : QObject(parent) { mFileIcon = QFileIconProvider().icon(QFileIconProvider::File); #if defined(Q_OS_WIN32) CoInitializeEx(NULL, COINIT_MULTITHREADED); #endif mThread.start(); moveToThread(&mThread); } IconCache::~IconCache() { mThread.quit(); mThread.wait(); #if defined(Q_OS_WIN32) CoUninitialize(); #endif } void IconCache::getIcon(Item *item, const QPersistentModelIndex &parent) { QString ext = QFileInfo(item->name).suffix(); QIcon icon; auto it = mIcons.find(ext); if (it == mIcons.end()) { #if defined(Q_OS_WIN32) SHFILEINFOW info; if (SHGetFileInfoW(reinterpret_cast(("dummy." + ext).utf16()), FILE_ATTRIBUTE_NORMAL, &info, sizeof(info), SHGFI_ICON | SHGFI_USEFILEATTRIBUTES) && info.hIcon) { icon = QtWin::fromHICON(info.hIcon); DestroyIcon(info.hIcon); } #elif defined(Q_OS_MACOS) icon = osxGetIcon(ext.toUtf8().constData()); #else QMimeType mime = mMimeDatabase.mimeTypeForFile( item->name, QMimeDatabase::MatchExtension); if (mime.isValid()) { icon = QIcon::fromTheme(mime.iconName()); } #endif if (icon.isNull()) { icon = mFileIcon; } mIcons.insert(ext, icon); } else { icon = it.value(); } emit iconReady(item, parent, icon); } RcloneBrowser-1.8.0/src/icon_cache.h000066400000000000000000000010111362250633600173200ustar00rootroot00000000000000#pragma once #include "pch.h" struct Item; class IconCache : public QObject { Q_OBJECT public: IconCache(QObject *parent = nullptr); ~IconCache(); public slots: void getIcon(Item *item, const QPersistentModelIndex &parent); signals: void iconReady(Item *item, const QPersistentModelIndex &parent, const QIcon &icon); private: QThread mThread; QIcon mFileIcon; QHash mIcons; #if !defined(Q_OS_WIN32) && !defined(Q_OS_MACOS) QMimeDatabase mMimeDatabase; #endif }; RcloneBrowser-1.8.0/src/images/000077500000000000000000000000001362250633600163505ustar00rootroot00000000000000RcloneBrowser-1.8.0/src/images/amazon_cloud_drive.png000066400000000000000000000105351362250633600227260ustar00rootroot00000000000000PNG  IHDR@@gm!$IDATx{Ve?Ǚq.:k6cf^7WlMUɱKeRJv%UEIHXq(ĈDCD)DB EDDdμ;<99}<<B!B!B!B!B!B!B!B!B!B!nP@5O3,e+8U񺴻\߲LcQe'&Q-vìa 릊)?sKN&dO~,EnxeEG9S?O!2ͼc^q'zۨQJß9m{D;>a(IlLR{vPdEpvf(aQa;ihPDs=|>'RJ=~JnRqBhc4w#ʔRX!B!_FN (᧘#!3c? %9Tp>yx)a+:r!yk{bI} H?V%=L|3<:3<̈~GpK<<^T0q9^@͞1G˸XqK )fhY!$xF*DMϞE?D*@;nձIJZju: xjY a GQ=[#!lEPc+mVVi&t8m**JOK {Yo/ZY,1IZ5+-,DLZ{ k7K},-lQjA>H [7ݾ\eaFXb2h&VؙѪ!ָbxΣH%`4(w04ό=HFVInpBVFm8# hcRѸ $`40Wp0:X{I@==  KPc(}%Ȭ/9Wvqk>%ﬕ%`$)*c:y3%TӏLfJr (lhYB b*ﲁ=,I,"H@!B (P ($PDJ7O1LE SЛgB@#$`̞o5 g6q{8-IIl%#{i. cywB cQ!JRx>7XC` >3X:J!/q0VK@idkHF$`:/q*"I@WP=A _[L{䓀ȷ! o܎|0䌱pZIp哀!O1xJv:#$`Ȩ姘'CĔJ@_c( E쏩~0Tq.I]._%WhX0[8M9(#K=7<yFJD)#JegPQ0=߳*R qow!V A6XͮO9*#]g#=Z ((%n2Ӈ# %`mIuy>$`$y|[0Bpق~_Sc0Ḇ.,#C|ZFua j ʌw‡A Ya$n /C E.#TÉn,r  V;vI= Ӹ`06 (avGFch8%HLJ7T&$HE)i4tSp@3p Pr),2}tc fnc#ncp J~j8 YE j8"fi5O orIb͆ӷhU6Ip .$&1]?RP8; mv1j;V*TIs?lib󧀴cK^&0B:q)QL7TBG%)lHBC.YW"!pJuBW% >oM_ C?T``yx|!]g}S=>S`esRL9M/zJzi'GB[syxTX(Z>?APbl#`w)YLPLlo2u_?*X~Y_0Y롍^tTKvF~g$1@'C]+)2sѐtL+)ᬯHr QZ8ۯ̗(E񆧽>+-l\?Je Z*|[򎔰swƌ*ϽA0<ҧZQpTAɥ}Qfi$u!õ&qٶ {*|I?eXΙ ]%aZѓMxǓln808cp_)Or!& %)C}JJc\"W>* KX-2P943F)SFPM?ƱCw%4F4iRZx) r,_i!?;MttCLpܑikSٱ+E[|N*~S"{.ۮ~z+ǡ80#ﯵ*)ώ xԝ XrE]:A_ѸM^W$Mgݺ 'wkU4b/'͔sDeQq0N>V}惔ru9HS{Ҏ0@Y\tǿMODzmVe$h.I+mO)k8;tVn,I')RWirkL&۳\uѧW-ê$z9T!dM)vܿ)M9lDdqR Ihܜn/1ijŒfYHD$z9n{olۮ6GDԔ鸜9_d9u/=-YWo|W<6Л]k[7yN{z5Ӂw/tmH<ߩL.MϘWGktmoeMttt}ثfz@>,ȣZ{F5v{[~[gT23*b8,6t.Á] `z/NV"+y:2]:^g/f6ҳߜeig'8uXxT`w;%N Oi]cc>f<5};U/ǬhAu73.3ٷEY]?g;a!1XB#ٚ ^k곌hWQb;bl[ѥ40Ez"۶0*t\EbL9zULa0(d1419lǬKAvhc=8vc|IL%FDuTRFPLEQBTӋ>|&Iי9S Dt0IDATxmV=3αF13_O-4=*9uT|#_H)ypQEIPχBH$1DB$QDDD|0:}{_gf_{_{k]Z$E'=IJP$%IJP$%IJP$%IJP$%IJP$%IJP$%IJP$%IJP`*-GX8)S>{Z N~) xY l؏̠|8{ƶg.]`zecglr?I+N<"~^*f;z @'Lm;?+b-L>;q'a/ Ќuubԗޖ@g9M3~8v:=T@삟sb?G@J9vҿoUp)_u[p$+0*"ɰ&9vʟ/Kn?,#/HӇJI*q%^HF$f h5dm߁.;_-nM0:})iw]/xAFSDM?=)JJƱkxB##A ot=Ŗ1luHյ_u-:KxEh[oXo.%9Yo_-ⷙ<\n ЮwƂ<]lRhS,5x K- RF8hO774_RhO?ZP=6YshK[x1ƃ;tXXp(3ݣ%Z^nMVo @?f4_9[(-i 0MVApHZqc!ɨE7b#ˌ}gw5O 撃xYfGRY]zSJB=y949x S)J7Ø|ֲJ(m`YF g.`LeIP @(P @(P @I @_TD9]s `c&)у5H+M&Vf'OL&Ml7gݳLf{$p X 0Rfa! Ī14C 4D @GUk}-1 M4^4>t(2/ P"Z-u4"=M>FZ8R vU`0u,I i@>0,1R7~(Dowu$` T0u<@jB;&|e_0RuM]TRq+b.4PK)Uw@YV~ @/,\$rTz`ﭣw)tW!-f}8"|7،V @Ja [LHC-᷶7Wwy,=+}8~H2 nk ;BEz bYNF^Xd@C @/d8یy.l{KJ`7}p$/]:` 0>5»! @d4;hz ntACӨuaQ+7<1}t@ӃSQ O^5ihV Kb+Ь^5-?hVhVs  ?R&g.FhR6#FIxۨ_Zt7 m5]lXhNld9|9?ZB1زaДZCMÞd$UF& װi]{j®ftJքuF-hBY _oBm,,-2~ '~@@?`Zi-|{BSGȎabֆo]VXhyj10/qY z#MhضӾVV0u56!+8Y #ޤ)Cx[-o=Ymhw>Pe)Cz,Q]F^e}l|`8u%cVX+nAwr wK"X*QALDQ]1(oL0jLZDR^YpOmT&,uM9:%N/x6u wr:[hZ>?Y]L67_*2*Vb3}DaNB<"*6Z^:Ԁn{qtg$s ^:2/9.}1t!1OK*{)Fu[:&ۙG>!n06YyK>o.GJgټ׺sLp;v~ RIg.f}\?twreu7܏Oin z=@wë!f~M{,~@ ?gޫ~?9j9ټMUkz/9fr|lde/ v>`SAjwis[pcカxʃ>`d3i#m~/wLf X*yLu̞> b<+^a7_!~L|c^.5Py `Ef#3ɳ19rYU٭.`+SOGyN˨;=PCEټ8+=aH zxӰ'eedZEL5pԦ)';Zx5:|LsJ]1L5/v_G0D cKS %Bª6WĵOi"zpb1Os,4PM9m"jz3] {BÒʙƻLd(%f%Oq^q?QzL黸~gVd|}pMUMpN܋>`;I#B'__ ٗث*M](ZT=X4M:kZUPXaHO3~l~v~Sq =XTr#looOo)~X^ uC \0ji"O{YL VA J~ aIz26k-Ym6s7y3CjqxZF0 6:e"S*|0;C\r<9SٙeS9Խ-4:-MGUa[H ۶oП{hޮQmlsBf2RG]L?f:tX<-O?3Ff)èe40-Y.Gv9%D,8z ia,,dwڂ者q. Uش7+Q"t1b#? 2_;WЂ': ԛqs8>lq9L5H=TQAQJ %PF;=yAT>d ;8x~'9C׍:ّm{ Jb*^<}PHzUG$q량gk箭 l,]ẵ0'cd3lGbXGOnͮJ·ir6B w+DUp!*wsǍ:sc7 O"uZ?eا*x>5 6o"Pzb^?7T05%=6UMZ $ j'H@2ԉqs=ӌij5$f) +܎!}} G#hn]w̢O* H*"~KG>bj.tek\Y>eФJebI-; ;SG#c07k6OYb>0Doj(OdBJP$%IJP$%IJP$%IJP$( @I$( @I$( @I$( @I$(SWIENDB`RcloneBrowser-1.8.0/src/images/azureblob.png000066400000000000000000000030631362250633600210450ustar00rootroot00000000000000PNG  IHDR@@gm!IDATx;sTu7. !B.!$@n~;@[TKS+B /283pPȒHt 3_v_N&@/eog #bͽ=\^X3Yv#@+ғՌ =ӗ|2P엦uhjlVZrc/9%wo$<ږ#[LH/]H^y;a󺪼;Nf\ `KJV] `QKίhќnܴt*c͊%'bh|>^rYO."t9hQb[y*Qj @D @D @D @DAAAAAAAA        "@(^&;-}nj# 6ى?ZQ>j#}3o=:sok~@J%@AAAAAAAA        "@ "@ "@ "@ "@xE[~Ts)˙uzS>idFs1Y~L|\|w${r(T.g5G7ir37s5$Iv+9̥aO|߹>'Iҙc X7hf%QE^m=v?#̕T`Vn|L.d.ՌxXC>yb=f <OIDATxKO\uJoHr+RRnm`gqƯЭTN]B#&h .b5i39HR ӱ(1"@ "@ "@ "@ "@ "@ "@ "@HNr"2e5i g"7.Fw=ތf*lf-3ϵLg)[)fMd6+/!&_rqKRF*6z 3=Xf7f^r32~Kn=˭w,%ח+J%[l[rt3-96沒j\pg!kxԗ\w.JSI꒛R%7T{OnݖYr{UEgX͍̒, DEkȍd58x[cͅzz ir,= y)*~=ȵxD _r `K$lzDE-9%Gkwhi$YȆ%'bCKh.Zr4>G\??-9J pǒfG    "@ "@ "@ "@ "@ "@ "@ "@ "@D @D @D @D @D @D @D @DxuZnpY'uq F @D @D @ZM[<ߥFh /當:GWc    "@ "@ "@ "@ "@ "@ "@ "@ D @D @D @D @D @D @D @D @DH3t7#Yς Jd'Gr>3YJN4:|zxf r6sG~̏$Ϥ;},1Fx_%˼$HW.Ǜ~`v|\D泚5QE{l瓼Q[=xfr+zX?S~gy{=^b X{zTG?}|23g%k Xg;4og&˩K,2y'Irl=VR̈́\;ws7_z<,d-֣^r/wY2e%c:v]{2_s忺r_EvOt.r&3լ \9\\ne L2,f3`wt7#J? &sz= &\=̵dS`ӯe47&|LA-9 _Gl=fl[{F3]A3R[S(x.Cȃf 4l~Rz,IRz<dŧ<'@ىzJ 3X rhRFHA        $ Ž1IENDB`RcloneBrowser-1.8.0/src/images/b2.png000066400000000000000000000045161362250633600173670ustar00rootroot00000000000000PNG  IHDR@@gm! IDATxkVO|T]锪TBZZnscO*-(Ee%aR#e&"DH!k?&r\}_?7Orss$t'u35Lt@zeZƁ |="D^ (/%hzW+ wC&a-a4(BEWgMд_9GI= BtQ NjCB'"3jaPGH61*pLZȰMUhԏ#l5﨓![5%8{PML!+=5hVVdZ 18d`]pYqk9Ci[Ш4v3Bqq\ ;`Oo8@%-cShzFzWf2+G#,:\~\] 2?nJdBϯ?L0dmz?LƓխ gfaKg*CxLO<M~YgX*:Q~4$-<.py%bҡ3,й(3-e!7i~U~gt; U8?ӝسf}u~յ&y~-ٯ3=¢}ژD~XG]K$>OE%黬۟u@V&߄8qRr_H*?S+] ݙD~h';<˯wZͬٯ7NuK8+,ػw^Bi~W!λ^zXl(Eb7#oH YjHU~eGT?8l&,3DE߃+]㵫_ 󾏰%'Sm 84|=>0p{q:_L~X~?geqZP9cah* Q>.f//l=Ey3-n3=n{夠#&o\_g-.< 1gy%Ņy"2"qKv;ߴK~ᖸrY`Oi`5QJ~'=g:s3>8Zx0v|[#LG pL/,?eY?k[YJ~n9* @n><ߛ;kc_E9pS-fƋ"CYK2XTkSgPQ{ 4'(O0!-IԥW3 D}2yۇ j ^ԭI/5 OBC}u/#Dcj \A*zO38Pd3eY=3ej o3}9GǤPO:ofej]Kwv$ch%@$@$@$@$@$@$@$@+wuLv;“,Ծ n渤l!V*QmZ^e)~CIENDB`RcloneBrowser-1.8.0/src/images/b2_inv.png000066400000000000000000000077561362250633600202540ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp G IDATxk񓓜ܘT]b)U;hntMiiNںҮ uTZXQ EZXJ} 1RJQF a"A)"!§ddn}W^xNsNA@"H$ @"H$ @"H$ @"H$`uJ:9 ے㺃`H9f߀1u5<0.P9fU-lEE]ۯVSmF/ 0ݖܮ3`};U~"2I04[Y0 `mV%\Wg%zz3`=jJKoL ղmjQ^`TnD+9?zվ,ZTvs iNT{gUbW߸Hؔ6jq%-۪Tm( Ua%HHX) UhJ'|ꩤ6fR*͝S\P{T1Hգ7T?AZH7{e.3x4vOKYoZKH7* e~ҍ^U> tqRǢ4CCN0;/˾+?uZ-O/!+= gk 3\H ĕn=:x!OCz7>!~Rߘ;~S~rV~I) O@\'C!lC`(5|;yG~l[ ?:iY50ZgjW0~)Ky V0qy6 `7^/~-]Uj+X?.Gn%\k_voM~7Fޏ3\ky5?U2?^ {6.3a_v&Q~׵?cz? /2/,|!gLD%O,aI}p~{#@ ^kW,近}5j~m{eS/c=>.{uqx~3{`g `Q\6h=>/Mnv#b`7v#2<l 77oAM"o`Ņ<QA'w]|,hw/_ԟ,7|?r,hOnv h:~&~uc|L JLת y^M?.@FW+eᕼ&\>`]|R|E 7 lo]gJ+kC*߂+ms Cכ8`5=%|[_|W0khSbo['XS=C`mu$anB֬ukAMO`]u= `}m:> '$B jx`2miޔZ5`r_ Ivwv3`5oU什xZy0JzBÜiCwf:9+xgwqRL:q 906i,pFL=E 0Wwiv8fQ,p/g-}0V٫p*̶?*r&ᣜK1Wڣg1@  D$H@  @"H$ 0z܌X;я_{xyPi~恭@Jd @  h ?6u><߂uz0W%'>P+ZʱxO$  D$H@  D$H@  D$H@  D$B+wfIENDB`RcloneBrowser-1.8.0/src/images/crypt.png000066400000000000000000000043541362250633600202250ustar00rootroot00000000000000PNG  IHDR@@gm!IDATxu׽ws?mntWe,͒MH a9a?()146 EADP1EĐ QDƐCDƈ8qΊ{?s>|?sI$I$I$I$I$I$I$I$I$Iv49/%wS4ܐy}wi^:,>߻6F5YGߞmU;1Olk^kz z_Ⱦ*^tyj|s,W[U0K-3ruStkЕַgLY^N< ygⶶ6S7}l_?ϸ{Ud\dmi2jI~ rޢ,Aˊknq4TipME [7 Xw}/= AD^AXv \fu`/ r<,}w {oA^8w=j< lDa4m9#:xk>GsseUeGnxfYV:/ +{3awJ">K>хs%ont|i(;^kv&|rdn/cȅ+D ~7;S@|+}ѯ~U.w|O5b_Xkŀ4W[=?3{)<77B IWBҶ/ogHN̍dܳ7Xث# ܀J35g293]6 |DmZXkz>s8YRG />H[ lV\X_|#Sk /wdДοcprQt5K~sؔ梿-(m!8* >Oz8eZ^p)V~ ^~2][՟- ^ -׷yyW3& }V;S/9RNGͺ-Şm͙7OcgݓVqG<%/4Ɗ}V{GS߈ZRho:ͮ/Zu_ͮ  7nIDATxusv-um*4K6#-[lCfCcX( 0G Bd1CCED wD"|y=y= 2Pm^PTPTPTPTPTPTPTPTPTPTPTPTPTPTPTct:ܬ˶ܑdlXv|'_ɪ,Ы=9krKOl9Ytْ'[ّ5Ywa.ɮ͸r 7 3Y2Ώ0𣞑 7dWW{f_̽{[r85 `߮Ԕ=9 n޸;~.RsvƤO]Sfs5]Zr*]ٓ~g-w|rۿ۴76Լg{ߊom҅;93\TN2lKt1lCt5X{t9wXsOλzvXkOʑ!XYCK[ҧOr&}ej|mwaW2`;GμI6Lw4>̑kpxc>GsSeugf*rZ.E^OZ{oI> >+>ٖ'HWNcP0‡gĞќv=ofa;v c\VÐroV~`~<0f8,ߺz{8>;ʉݹf/pFsgnEsoƵt%m~y't ٻڎi70 ~ۆ`{>Ftv:M_,>+x:g X.>X}7\ `..~}l7P}qɦ,׋gc+, +,ӋwrE3yr҅ծkE-TK>?>YpWX3hk=tKS/'TڒG-XT)  ~wAޔ0pWloɞT+vߢdstKO-:xؖb_EYh782yU9кp~ iaxb]}َ*Gi QZd__oOmE~) Z/Y*2Z 8^Td`p]߇߂ o˜^Z/w\t>Z,lv#3p7=ըc_zOG\+gě+{sđձf6L̥9P{Z/2QIENDB`RcloneBrowser-1.8.0/src/images/drive.png000066400000000000000000000071131362250633600201710ustar00rootroot00000000000000PNG  IHDR@@gm!IDATxVU}tt1uj2FSKmv35Ҍܤ,0)I)(#)(A%P$1DDE$ADdq|39s}y}9|AkyO%]W[ժs:]RT zBUb Rj;ErCڤ%!*r֫ڪtHr" ]9FU9f3?L5tT2jP_:-^{"^q*L40MZ(RݑV{ҽth~t'ו=AڧH ;rHv) 6_XKW[o.^`;L&FlP]oQjN|KKv%TVCV'\la?֗9z %WMHw[Xe퇹='[*U&xR(`\$1F 9/ZӛݾY1Lj|!O^ZM'?5uc{f-[ W$,ve;v'[|erؒ =*߅5^|ϐ){Bo[d'+&6? ra{͋tBrܲ**.sM  8جa܅nv.@@3yZ9oqïp.rP̝8y"74+ֺMblY" .E@S1YO$Slu:ͽd+P|Vp#h,mu[]pqs?E@cYi 74/mu9}{[]j¥h,GmuX/!$6" a1vFf:D0sV—弭.8ȷ唭.jW[]>YohRVD'-?$-;lu v%,Ɔ9oh*Np2‹h*muAb7s:L  ?;o2'ZPkgPXל7yJNE ܭ}5],GDfOG@"df7!8|f[/8yγ /xhCh"c,vC$;%aP2Ra[ s6~҃qW6|U򖂛+}j'yy.ǰpqi؈awdP_.z-<Dz;pq[n$ arcŞfixy_]^BC[|O*]jOmiX-Iz|AڜW}2I$=3spNxyi ']{h3py_RTx\^yRr&HK*yZ <RJ뙑ԀS|MRQzR4&qӜC]{0 _j&jU% կJ7gWSz$蘧G/^lWU\\Ýx}G<챶âq޳4-]|4GHs< Ҷ,*2fDL]!`NJ^ͼN[{><$5.׬PqJdP?qE}zF}&^ˤ~P(]]h_UmL[B5fG ̏| >1 DSjRIkGGCZKq+CUbdtLjJ)=\qh[nЍãLJ,`J4"B*L,LiMDf*?2N_3`S\{Sf0yS*GWgb># MNb}X_Q0f%z߀|)P1g!SJH'38M )Ro(S-suT%+{/,Tm_]&y,+0:QwTg=/wOi*ؙ}jмvHFiZ yF v栖QUb_i6%YQJ\gnq_U*S 1P%*St&j7M#Ӻ$޻ReI#/seo"ӔXfJ4+P ֽ-hZ.OEΏsǐ!4^%CA[WA YrjA oq><| .<'"}T#b!&e"lj[dqC MW0y^1;/{Fd5OyVf1"N6P/r]H[ R[O>u3/$.xiM NOI< :qnh.D9'ԆpCvX] |q; ¥@F~j.'JEZ+ Y\?*acNdM~3w©:KER=ZXRtjԨ;1x+l<6TFykb}O|`UZ6qFyk&'ę{HVYL4Q Qi%da|YՠapAh^6]r Ju 5iܡ#ZW ;u!w{4Fmay]VG7ŮUgu\G_;NokNCT,Ib0!<IENDB`RcloneBrowser-1.8.0/src/images/drive_inv.png000066400000000000000000000126761362250633600210570ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp U_G3oIDATxkUW{y//_4ĨiiSFjڙZƆ-u*t-mt(JEQZĠRQ"""CA$Ha=}Ig{Z@{9gs౨G8Յw\' F'[WQF8u`Sn7%_}ՁPCQk]֮@vЬq=V? a@c]d+Cuc!~*v a0ɀ%m#GRp X6,ho.m$Jɍ:\.=dg5k_ջmf^0Dj~#a}墑.1fNDgτJHfC~З\ l#_Zn̦9gB03sÄ#:3!+&[bO3))~?M\MۥTm}8U&jd$GV c f pwc'dL؝Ç(GQ?;Ix su7ZIT& 4ItZ/l_/DUV=cJO98r>n:̯{jr W>VnPyh $g橚;>EW?#ٮgSj;7w?.Pe< E}ŝPu⤗: ~}3%8ed58s4~l${,1wvh986Ow#i.g'+>2?68Ix"~.Yv.sO 7{DR X4%~V?㗹l4`o9TE-GYo K{^6n5/_5 K=Z̲{/OzfaN?x/ Wz0DȪɣXN5`1 `oc1 ~_Vf@30-8뾴M𯺿9<_)Hv~;YB[Ha=沀Q/¯#\'>!Bղxx4GS~GY:1([^'T)07LtFS{9,LjjX4hsE# 0%#\)`KFyqUg`:G^X0CgUÈ\0p4B}9S Pj>;!j6|EL\ @Eڠ C"tF<*mma<"JQ].7p6T|]~Ip.7MR|I {*|]1>Lp%T z~Mi. TuhjUP.t3;Ptm8JL$6o@hS@< v]^JHtCg25p Mp_7a !6uHs]qO/EJ[uhdeP0i!* LBJ y~=\.ׅ{_WIE3p7_eDOx= \wkS 8]265&ǺU>C߼9D\p0$u0K(Mz9(ʐD &S3ߋB0i6=D@2t~\L~6co%LMK{_0ց4ie]WMF0>m'j1.}-"X&o`ׇ?fz R8&,iSxS_b~G3hհ=T4G]&7_X^,kX[mU?m:gQW"^R`MmȵWy=ΐKmUO#RLc*]S/\ܱY 3U׶ f5֎z4XѰIUߎ<>EtVq:7[дUs_X#b%ZΒC&[Ps!tyY'$|dGwv=c˪Oڡp:z"z+ZPBgu]CZk<,%GT꭫;z[:xvgtVn,3T+鯧{z/:dtPCC'ȫ\zaRWPNЃWJPuzR/ߵ Cq&&X ӲjD "/}(tnz C5 =3U j34@X.,o™H8{{qNT X :<|6c#m/)s{FS;}[) \Фs过c#ڱ h&%wSYK1,l;\؃ ݹpmNp65>pPrީKu~+xЈhGW/qqb f #,f Mi Mi Mi Mi&4MiH&4MiH&4MiH&4MiHvg7fdm_}IENDB`RcloneBrowser-1.8.0/src/images/dropbox.png000066400000000000000000000105501362250633600205340ustar00rootroot00000000000000PNG  IHDR@@gm!/IDATxUǿOtޘ+2_iIS4LR^d$$$((1(" !0 "0}39{9{t\k^k!B!B!B!B!B!B!B!B!B!B!B!B!ޖOp[ mXu |ntOd K4Vtp]V#e>xhM[PĭH >UdpFy/peQu. HJPﻪ](KYQcJ\ϲIzm25 s?I842?WՊ4=bOm2) I|?֥ qHJq"ºb 'c(6Ʉd@p*H@? B[M{e9,\Db@0@I20wc$yIc[Y+qd$#]Lχ1o 7YZoC5.%A2ڼ4(O@8} _o$JVw chJSMJGW@[92nd n*_DU8JM 13s,D4k2 2X Yq}, 'hI?i_!|䀤gos GElM+o"9rdkd_K H>rٷPܻVZ5@RsWyŽ[i-q$9ȕVHV% Ye:/GۡII ][f^NơM V@S/;S! Y7X\O]_(8S!\Lӊ+ۗdދ"7h_/ a2"yE!8C_T;틂*'Cپ(t8J>h/ %BE#CڊEfPETB m7iRD֡:n(rL@\8*#u`+C6T^M %ڭ43fH }}x\P |udkVڎR* Pl>e*<ޠFwEl ߢt Uk ؀kN 3&RcMzjhXk-k"˳|}yvŇ 0w T=!\ēkSiZ>B*VW[bQ F:XXYIb>BP=JOHG{\/Z}lt\#z~rh_g$';}l^4P?eثLCV|KȻFeS\{k Di7  Q4>6=#2ҀBt3 ts[nbCa 76GKg+-Q7o*x]v]sU1s(7d*B1D/M0%w46H//QyLoQJnC' zᐁz4o g Ts{cRXwC.9kdl;ZvjJMLmWOa~sVslEI:)V+2G EƤ׌d%{e8cxލM&PKNKk%#/v|iJGΓwTu>M"_*m-j;橔^#[x= xOcӜP(n(]DN(T:6E&D޲Cjx¨~&;g d#pá 8er:x^f&چ # %Nz5l 5{̈́4V9 `d&̩ eAǍx ^4ftB7.Q`1Y%f>PERfn· XRx"tgYR+n^yĪYk`e̳sz{aI %i6̂U>bJ1}"7JirܱN`UG1yuEXS}pxqVIR liJ$CсMX?y'-¯,J#ʼ gR ߈_Fw/,>#D;OiXfT-/$ƺ!R'h/x?{T8,_d7x:9i5X`%Yg x=3gy|Q_s7h|#nQUL3(%[酋,My6y$Y`%q G]x񌝻y9l _J~6G Nf7OS}X%s  <=eHZ4%P$9q/@:[c}/|g6O <in_C~8 t31,cF][}2m_%1R\H;_Y`Z ѸSQ=Z}F-b=%~Dk..ĦЁG kWB; uVC$:L׉3~Guh_|%Lj,*~$dd}@Vg <QqFVc;a 0[ոx>(ghƮ,V,$Q (ٴOQgfwz1"M%NaKZ1y KnTw5Fy\wxty$B=7d|#&Ƨ 0k/ӄ[K Bs{`NC4[&0ăE^񹤆81_'h|'1*&٨J0neJ̇&}o8`|OՑ9_#&Qe$n0/IBb|PI5$z㻅TQd4v3 :JwIDATxUϽ3?KtWj=G4S_i%+ҒRy%@$%JFJR(Jb RH("  0|茞sk]?qfZgeO E)HQ"E)HQ"E)HQ"E)HQ"E)HQ"E)HQ"E)HQ"E)HQ"(H@"(H@"I<"n(O@%PZm'> C]sPm%2{>mBJN|S^$T5grj =LH*q=W }G1\MFK ^5b  BaFB~ߎ[vwp'Ռn܄ ~ARΓN?Г;hBq&5!Ug9|sT 1 <%Gl4CJH u Y~wtjMNBR?vχa2*j\jl'$cN<:Tm6ڽ^vlO0; Iۍxw # ` 0g8m?[Rf>iܿq0nv1S + 뙅>y[۰g $$K,'yK|MoB=ռ% )z]NH:)5oI@IH0X9м%`MɄd@4oǯRx B}<4Q';!))Y[jy&~}Lqj'$qNWM޲`nXq I5K[v "ג&$J&5pϒ$$_:- -ءK I pI:`3\ Iҫ\\9F[)/< _<鍧FL717] ? OHJ4z7]Ci IW\&"q>[d>nvRW&H&$SSu4o_3 IR㭆-MK&ۈؘ*7oů?.Q\4Se[*JCh‡vD6U5oo).2C[OS,T#EJQ,6߼) KOC7vl;%qp;'""ˋ &Iy6VVy鐨߽ث:`YPK"?+;9r`/YL|~7c wx/jo$_ߌPn0w/|֪n*Aϫ}^V]!<̰TW}L_enj_W!l7τTj_}V皠;e8jzŘT|~d1[ SD@>*WL72OEϏ ~S Yz}W;rېT\k{7hb*z=eE{8Q!gx[A^j;lj// ڭɯ6b?m'BxOffʩI5`˫ɫ.띄Ҳ!lĿl{&#w}2*w~ P)ٮV0@)٬.⑬Xnнj a#^㍴H]Ba;? P=sڧB@B!>5)(jRZ+BXGnu7j_1.D˝Om/Yp;ŷz4y2AuQQ<+V0PX[M~5dOS{k'b1C>#rU "r482V"Z]0a xۤ>r%:<"~xwE_`!GN*U]C+R+nǗ Dncq|3A0¢wM |]n9Hn> f8 jPTÿ v O{6iŘF-)׾HlR #Xyc><80*q\Sv.`#uη-d5_s NB uSc3wunc$m`;0S f)g;A;f.xἅ\Dt<– E.k'9>X`<EDgݠ c DUX (6 s.cno"!;bc*Cd`S~}0̖]k4m]Prۦ?]pqZ|dΡ󋲕FKʑ;@og% n9]0ph/@tEnP_'Yi͘m!E(egLgq.[ j9Pt3n8>(WrETfJ0uEQyÙZ@~QnP ElzLہ"[vZnP>Z!]*d'T{WYOعlOA OȷM*v*;Mք*4|Ȗ* =v(xl4uʰ>Eq9IHqB?>$RgE2epݼ8"oRAl!ŢTyNe礎W)zk)=_q4b K4E@]}L1iz#o| ^3YG|y^?epkoj~^YERnƇHyrT\8ۄmExwjyG5y^ABLIx?ROPMpNY=b=|N{78xkPʱݫģ {x迼﨟F:6":xW4clԿJ>ʰуģ-`smqţxӉǃ~}OZ_Ra+(q`DYxd =\okӑ+Y*Rqbd Ƴ6 `,8x+4O$3!Ya[01^}BRl'R` f jZJ<>{CT8(2.o`cBJGC'f6X `vLYN/1ge1[ŵ % k"m{#nuh%gJ<J:Ӧ+.YP0x]}cNAo?c V~ h.5c箄^ cme&̊h; 'n+l)3xRsg/˲|NJGw `J֮Xl8j`!g/ D]?.*]0.묨c;Bnbi\z>?%J>]0Ntַ*f>iǎd !l9~5L(h4 *3^]@lD˵Xs発=76Z?d`:n®<bj1Z $zI*>-s]:2x1v|1~F ;^T8^|uϘ@w+.PJ*|GQ8@cXƇ=r.b |ѓ1!~pc hKCQ(@R E@R E@R E@R E)R E)R E)R E)R E)R E)R E)*BQbIENDB`RcloneBrowser-1.8.0/src/images/ftp.png000066400000000000000000000041461362250633600176540ustar00rootroot00000000000000PNG  IHDR@@gm!zTXtRaw profile type exifxM#) b$qH~"syiꈮ,Tx`;.*!y.9G\uӮn=zF+h|R]/Xdz=X;;엣͡ȌN vCS\ٸ,d+A#Bwg$AR]5c`}B- W_ /.9NGi.;m>fD|32>8gWS+_Me0&ٯeGѷ] uLM!u4iQCZŸpP*4tai '0=qˊ`ȝ0 WO˓92'~ yZ_Hc[5F}5/MuKl 1šX[9KԀ)_ b+!I2Ec6"S9KHrGD2`7 61cY4x,4:XB5hRլEk-sX2lfnŪ'Wn^.cLCŊRjEК*|U0|ȑ=a ˧-7kJtn{uRiÆ2Z2ԙMe;3WrF5ޠ8{Pqq"E /rY,,ADYi4uҝܷ݃Sn ra@3/[ـ.8!W~Ԇvvvvvvvt43Ғ=غ#iCCPICC profilexJPT*:8H 3T (X&)&0 otp0BN´\ޅ4 .GWv5z 2x Kx5yV UE־ؙWaۡ(~QE'NFͮ&f#.M_SLQӈ?p-&Yx˗Τ.g%}`曼_KG6*<`cl i eu6gQ)Ws{Bf2K/X6esȵٕZOȨ}sW~TA*9=YP>z|g+!zcCˬof ^RW ] 7!D2 %/k~@겭Bd@겫 C*VD{hdZp-W*9#<2Lֆ^}:|PTZi66@صe<[-sSvfO +B;f^gH)JfZ9do`3N|7FrGjs˽a{{X>m{-="0y&r,lLe2otrjcOegm߳+72hLîDt{WtT~ʏ|ś=Yvȥڴr!YsKN?%p)~J^.:3St* O!ZA@DA@?L\PIENDB`RcloneBrowser-1.8.0/src/images/ftp_inv.png000066400000000000000000000055471362250633600205360ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs.#.#x?viTXtXML:com.adobe.xmp fIDATxܿe;G/H.'QB0 *D;!"SYFĨX?@!ARI D|.Q޻w{O}<3 z9PJ @ @((%PPJ @ @((%PJ @ @((%PPJ @ @((%PP @ @((%PPJ @ @((%PP @@ @ @((%PPJ @ @@<ǻ \5?|R35.Ӽ;p)k0^Sٕ9[Ypp!v0?f/y+`g:w2 `GM~m3Ʌ,`=i;ϝ޼c5Vtfɣ9wrn}iW7qB~'g߇Fy/+2cT%,AJ@W $A^)2 p >`'zS.|@ >*undMk_l$.lI8 m`G' f=yÜȍs |W8'p\́ɫy?>p261`=|lrG4e_85 Myjg~5uewOĸ}WNgwռ 3lJW' >Mׇpc˹s2s2OnۮlpCVs\a\X"Ó뮀X 8@ ල       J @ @((%PPJ @ @((%@ @ @((%PPJ @ @((%PP @ @((%PPJ @ @((%PPJ @((%PPJ @ @((%PPJ @@((%PPJ @i|;IENDB`RcloneBrowser-1.8.0/src/images/google_cloud_storage.png000066400000000000000000000071131362250633600232460ustar00rootroot00000000000000PNG  IHDR@@gm!IDATxVU}tt1uj2FSKmv35Ҍܤ,0)I)(#)(A%P$1DDE$ADdq|39s}y}9|AkyO%]W[ժs:]RT zBUb Rj;ErCڤ%!*r֫ڪtHr" ]9FU9f3?L5tT2jP_:-^{"^q*L40MZ(RݑV{ҽth~t'ו=AڧH ;rHv) 6_XKW[o.^`;L&FlP]oQjN|KKv%TVCV'\la?֗9z %WMHw[Xe퇹='[*U&xR(`\$1F 9/ZӛݾY1Lj|!O^ZM'?5uc{f-[ W$,ve;v'[|erؒ =*߅5^|ϐ){Bo[d'+&6? ra{͋tBrܲ**.sM  8جa܅nv.@@3yZ9oqïp.rP̝8y"74+ֺMblY" .E@S1YO$Slu:ͽd+P|Vp#h,mu[]pqs?E@cYi 74/mu9}{[]j¥h,GmuX/!$6" a1vFf:D0sV—弭.8ȷ唭.jW[]>YohRVD'-?$-;lu v%,Ɔ9oh*Np2‹h*muAb7s:L  ?;o2'ZPkgPXל7yJNE ܭ}5],GDfOG@"df7!8|f[/8yγ /xhCh"c,vC$;%aP2Ra[ s6~҃qW6|U򖂛+}j'yy.ǰpqi؈awdP_.z-<Dz;pq[n$ arcŞfixy_]^BC[|O*]jOmiX-Iz|AڜW}2I$=3spNxyi ']{h3py_RTx\^yRr&HK*yZ <RJ뙑ԀS|MRQzR4&qӜC]{0 _j&jU% կJ7gWSz$蘧G/^lWU\\Ýx}G<챶âq޳4-]|4GHs< Ҷ,*2fDL]!`NJ^ͼN[{><$5.׬PqJdP?qE}zF}&^ˤ~P(]]h_UmL[B5fG ̏| >1 DSjRIkGGCZKq+CUbdtLjJ)=\qh[nЍãLJ,`J4"B*L,LiMDf*?2N_3`S\{Sf0yS*GWgb># MNb}X_Q0f%z߀|)P1g!SJH'38M )Ro(S-suT%+{/,Tm_]&y,+0:QwTg=/wOi*ؙ}jмvHFiZ yF v栖QUb_i6%YQJ\gnq_U*S 1P%*St&j7M#Ӻ$޻ReI#/seo"ӔXfJ4+P ֽ-hZ.OEΏsǐ!4^%CA[WA YrjA oq><| .<'"}T#b!&e"lj[dqC MW0y^1;/{Fd5OyVf1"N6P/r]H[ R[O>u3/$.xiM NOI< :qnh.D9'ԆpCvX] |q; ¥@F~j.'JEZ+ Y\?*acNdM~3w©:KER=ZXRtjԨ;1x+l<6TFykb}O|`UZ6qFyk&'ę{HVYL4Q Qi%da|YՠapAh^6]r Ju 5iܡ#ZW ;u!w{4Fmay]VG7ŮUgu\G_;NokNCT,Ib0!<IENDB`RcloneBrowser-1.8.0/src/images/google_cloud_storage_inv.png000066400000000000000000000126761362250633600241340ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp U_G3oIDATxkUW{y//_4ĨiiSFjڙZƆ-u*t-mt(JEQZĠRQ"""CA$Ha=}Ig{Z@{9gs౨G8Յw\' F'[WQF8u`Sn7%_}ՁPCQk]֮@vЬq=V? a@c]d+Cuc!~*v a0ɀ%m#GRp X6,ho.m$Jɍ:\.=dg5k_ջmf^0Dj~#a}墑.1fNDgτJHfC~З\ l#_Zn̦9gB03sÄ#:3!+&[bO3))~?M\MۥTm}8U&jd$GV c f pwc'dL؝Ç(GQ?;Ix su7ZIT& 4ItZ/l_/DUV=cJO98r>n:̯{jr W>VnPyh $g橚;>EW?#ٮgSj;7w?.Pe< E}ŝPu⤗: ~}3%8ed58s4~l${,1wvh986Ow#i.g'+>2?68Ix"~.Yv.sO 7{DR X4%~V?㗹l4`o9TE-GYo K{^6n5/_5 K=Z̲{/OzfaN?x/ Wz0DȪɣXN5`1 `oc1 ~_Vf@30-8뾴M𯺿9<_)Hv~;YB[Ha=沀Q/¯#\'>!Bղxx4GS~GY:1([^'T)07LtFS{9,LjjX4hsE# 0%#\)`KFyqUg`:G^X0CgUÈ\0p4B}9S Pj>;!j6|EL\ @Eڠ C"tF<*mma<"JQ].7p6T|]~Ip.7MR|I {*|]1>Lp%T z~Mi. TuhjUP.t3;Ptm8JL$6o@hS@< v]^JHtCg25p Mp_7a !6uHs]qO/EJ[uhdeP0i!* LBJ y~=\.ׅ{_WIE3p7_eDOx= \wkS 8]265&ǺU>C߼9D\p0$u0K(Mz9(ʐD &S3ߋB0i6=D@2t~\L~6co%LMK{_0ց4ie]WMF0>m'j1.}-"X&o`ׇ?fz R8&,iSxS_b~G3hհ=T4G]&7_X^,kX[mU?m:gQW"^R`MmȵWy=ΐKmUO#RLc*]S/\ܱY 3U׶ f5֎z4XѰIUߎ<>EtVq:7[дUs_X#b%ZΒC&[Ps!tyY'$|dGwv=c˪Oڡp:z"z+ZPBgu]CZk<,%GT꭫;z[:xvgtVn,3T+鯧{z/:dtPCC'ȫ\zaRWPNЃWJPuzR/ߵ Cq&&X ӲjD "/}(tnz C5 =3U j34@X.,o™H8{{qNT X :<|6c#m/)s{FS;}[) \Фs过c#ڱ h&%wSYK1,l;\؃ ݹpmNp65>pPrީKu~+xЈhGW/qqb f #,f Mi Mi Mi Mi&4MiH&4MiH&4MiH&4MiHvg7fdm_}IENDB`RcloneBrowser-1.8.0/src/images/google_photos.png000066400000000000000000000071131362250633600217300ustar00rootroot00000000000000PNG  IHDR@@gm!IDATxVU}tt1uj2FSKmv35Ҍܤ,0)I)(#)(A%P$1DDE$ADdq|39s}y}9|AkyO%]W[ժs:]RT zBUb Rj;ErCڤ%!*r֫ڪtHr" ]9FU9f3?L5tT2jP_:-^{"^q*L40MZ(RݑV{ҽth~t'ו=AڧH ;rHv) 6_XKW[o.^`;L&FlP]oQjN|KKv%TVCV'\la?֗9z %WMHw[Xe퇹='[*U&xR(`\$1F 9/ZӛݾY1Lj|!O^ZM'?5uc{f-[ W$,ve;v'[|erؒ =*߅5^|ϐ){Bo[d'+&6? ra{͋tBrܲ**.sM  8جa܅nv.@@3yZ9oqïp.rP̝8y"74+ֺMblY" .E@S1YO$Slu:ͽd+P|Vp#h,mu[]pqs?E@cYi 74/mu9}{[]j¥h,GmuX/!$6" a1vFf:D0sV—弭.8ȷ唭.jW[]>YohRVD'-?$-;lu v%,Ɔ9oh*Np2‹h*muAb7s:L  ?;o2'ZPkgPXל7yJNE ܭ}5],GDfOG@"df7!8|f[/8yγ /xhCh"c,vC$;%aP2Ra[ s6~҃qW6|U򖂛+}j'yy.ǰpqi؈awdP_.z-<Dz;pq[n$ arcŞfixy_]^BC[|O*]jOmiX-Iz|AڜW}2I$=3spNxyi ']{h3py_RTx\^yRr&HK*yZ <RJ뙑ԀS|MRQzR4&qӜC]{0 _j&jU% կJ7gWSz$蘧G/^lWU\\Ýx}G<챶âq޳4-]|4GHs< Ҷ,*2fDL]!`NJ^ͼN[{><$5.׬PqJdP?qE}zF}&^ˤ~P(]]h_UmL[B5fG ̏| >1 DSjRIkGGCZKq+CUbdtLjJ)=\qh[nЍãLJ,`J4"B*L,LiMDf*?2N_3`S\{Sf0yS*GWgb># MNb}X_Q0f%z߀|)P1g!SJH'38M )Ro(S-suT%+{/,Tm_]&y,+0:QwTg=/wOi*ؙ}jмvHFiZ yF v栖QUb_i6%YQJ\gnq_U*S 1P%*St&j7M#Ӻ$޻ReI#/seo"ӔXfJ4+P ֽ-hZ.OEΏsǐ!4^%CA[WA YrjA oq><| .<'"}T#b!&e"lj[dqC MW0y^1;/{Fd5OyVf1"N6P/r]H[ R[O>u3/$.xiM NOI< :qnh.D9'ԆpCvX] |q; ¥@F~j.'JEZ+ Y\?*acNdM~3w©:KER=ZXRtjԨ;1x+l<6TFykb}O|`UZ6qFyk&'ę{HVYL4Q Qi%da|YՠapAh^6]r Ju 5iܡ#ZW ;u!w{4Fmay]VG7ŮUgu\G_;NokNCT,Ib0!<IENDB`RcloneBrowser-1.8.0/src/images/google_photos_inv.png000066400000000000000000000126761362250633600226160ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp U_G3oIDATxkUW{y//_4ĨiiSFjڙZƆ-u*t-mt(JEQZĠRQ"""CA$Ha=}Ig{Z@{9gs౨G8Յw\' F'[WQF8u`Sn7%_}ՁPCQk]֮@vЬq=V? a@c]d+Cuc!~*v a0ɀ%m#GRp X6,ho.m$Jɍ:\.=dg5k_ջmf^0Dj~#a}墑.1fNDgτJHfC~З\ l#_Zn̦9gB03sÄ#:3!+&[bO3))~?M\MۥTm}8U&jd$GV c f pwc'dL؝Ç(GQ?;Ix su7ZIT& 4ItZ/l_/DUV=cJO98r>n:̯{jr W>VnPyh $g橚;>EW?#ٮgSj;7w?.Pe< E}ŝPu⤗: ~}3%8ed58s4~l${,1wvh986Ow#i.g'+>2?68Ix"~.Yv.sO 7{DR X4%~V?㗹l4`o9TE-GYo K{^6n5/_5 K=Z̲{/OzfaN?x/ Wz0DȪɣXN5`1 `oc1 ~_Vf@30-8뾴M𯺿9<_)Hv~;YB[Ha=沀Q/¯#\'>!Bղxx4GS~GY:1([^'T)07LtFS{9,LjjX4hsE# 0%#\)`KFyqUg`:G^X0CgUÈ\0p4B}9S Pj>;!j6|EL\ @Eڠ C"tF<*mma<"JQ].7p6T|]~Ip.7MR|I {*|]1>Lp%T z~Mi. TuhjUP.t3;Ptm8JL$6o@hS@< v]^JHtCg25p Mp_7a !6uHs]qO/EJ[uhdeP0i!* LBJ y~=\.ׅ{_WIE3p7_eDOx= \wkS 8]265&ǺU>C߼9D\p0$u0K(Mz9(ʐD &S3ߋB0i6=D@2t~\L~6co%LMK{_0ց4ie]WMF0>m'j1.}-"X&o`ׇ?fz R8&,iSxS_b~G3hհ=T4G]&7_X^,kX[mU?m:gQW"^R`MmȵWy=ΐKmUO#RLc*]S/\ܱY 3U׶ f5֎z4XѰIUߎ<>EtVq:7[дUs_X#b%ZΒC&[Ps!tyY'$|dGwv=c˪Oڡp:z"z+ZPBgu]CZk<,%GT꭫;z[:xvgtVn,3T+鯧{z/:dtPCC'ȫ\zaRWPNЃWJPuzR/ߵ Cq&&X ӲjD "/}(tnz C5 =3U j34@X.,o™H8{{qNT X :<|6c#m/)s{FS;}[) \Фs过c#ڱ h&%wSYK1,l;\؃ ݹpmNp65>pPrީKu~+xЈhGW/qqb f #,f Mi Mi Mi Mi&4MiH&4MiH&4MiH&4MiHvg7fdm_}IENDB`RcloneBrowser-1.8.0/src/images/hubic.png000066400000000000000000000043511362250633600201530ustar00rootroot00000000000000PNG  IHDR@@gm!IDATxhFV*ZQuqEU9ֺFg#NlʤRiQHR*°R")bHDDB a bo|x{rw}$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$IRݪw,'םNuՌrWu")sgmt hbi9ǻn>8;pwF%>n@wS[T .T FPG##T*bU=@# #T[ FP$4 2AA3 4AuҦB FPG#-AAAA *bh*bx1j1"x#TG#($F*#̱X`Mɓ@s [#Hغ $l39=πe_"`WkT Xxf9V3ĦeT^dl#HwY888F.qXDTGuP3q^Ƒ1ጧż~.>_ULwtIo,Y )r-,bmk9:_se+yDSX?*ʵLc?10q0GQ ]1c;/1A$IZƷ1&'sSx2_U`R6738E(p=IP@k޿>>xv1e< rQ,d'XXݹzSTC%iڗ:[=.'}2!`ձm]fy:1A!iMOm# p"sw闦oW}BgqF x<7żQs8-w 'SG"DK1DPSД"в=3Y8EjSTvO2&E7E_$NU}}л>#8a# Sq8kC Ըgk+8.?JSۭNj{.ɷ:SE?KJ&ESTu^5iYKkkBgw{F hINÕnm4OkAW `}x֝:U8N:w&}n-&,Lc{Ui~5sQZqpp xݠO _^e"/YƢ4?0-]=Z[ug+O:ue6|⓶,6r1ͧ,ci21_x?l3<t|VSC 5llgqo2pko}(0m>(qd:CllZ5q, t m^mEVyU-RTخٕ%9r}۔]w̳E!y|1$ɇ|~;ŋ[ǐ70~.ʝO!1) OCcS8;Bz)cºb1OyC!O?e{ 1~x!O㧀cS,3~bp 1~O!0~b ㌟??u%㧐iҢw)RO;PƐu<CN10cei9Ku)r7(wgƐ;4p}c.M$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Iߤ~ۙ+IENDB`RcloneBrowser-1.8.0/src/images/hubic_inv.png000066400000000000000000000100731362250633600210250ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp y; IDATxhĘDUWtZ5+?u֍M7: UTTFG+IeҢ Tb)")bH""" a$|򼸻re? h&@Mf4 h&@Mf4 h&@Mf4 h&@Mf4h4 L&@3h4 L&@3h4 L&@3h4 L&@3Mf4 h&@Mf4uVoY̏(5cd)R4yP g[OW5Tz칎չ2=Z Bw<{giZX ؞Y 6Pܼ wW 0 "A^A$(+xHP^A$(+ح AHPW UQڑ `'`> 0D HP6$(W `'A  0 "A><'HPW jHPW S Aw`V:bm279`+y&Oa}e+דx q`t_ymj6^1Ɲ|jαX1.f hqX8G7g ciG>L}R.WܻG6yg?l[A*8ҩe=5xs?*4=~\h]AR;.xUeWlfE%c?/ϭ.CR7>T2I3X6^ ALc9G4=E8V,h#~G+~8GzWή/p|[Vs|8o@KP?ٯg(0\VgrK])ͼi=^%Ngfxgt`| mۋ|Zzg|WUY/NfAoӞYRlP@9RuZ9Wu{gx*[~qiPG{Ӟ q]f.IG3;,*p@nvq_,5=J:{N;fj b> ϲiWK}=Hs3n3g`'\`˨ /K<X bœxsg.#z)629:"T2sW^ȯp;!eo2ۥ"?k}I=O|8f3_J =mLʩ_}NV05_21s%It*3$͗x?2)OKRjfR;u?Mm~CLv'_hnraD?P:vw-YLj~Lg#@wOʛ&?d~zzaONp%X:6W8W <ҵpXBM\?({/U<;X,+_0&ERlXn|&5#]zKk |Ozwe߇m_ƿ|H٤ p\adؾO!̐J^.׮yrɷ22~^4O!-`O^CkN 0d/Oy\C'@(` OyYC'kp "?v 05D~ O S-S[C'kإO!!?Oh5D~v']~ ٢í! Tx pj 9(Va֐+yvfy \5=}s|o֫ɡ2AJP%(@MP &(@ vH P(Ani7AJP%(@MP &(@ vH P(Ani7AJP%(@MP &(@ vH P(Hr!{HMEt3{ dEg3eYex\=_> K34XSX6#?D0 X¬7'`Mw9kYSkWnii\Tv殉ng3i _[Ǿ&AAAAAA        4AAAAAAAA        "@ "@ "@ "@ "@ "@ "@ CKAG'v>NOfvs~vιQ kvc&G)enAB%`_;MgRb˭l=Kb垄 5n+_Gq߲6P{_rς/>_XvA^CYT#^ 祟Yy2 9iWlwIENDB`RcloneBrowser-1.8.0/src/images/local_inv.png000066400000000000000000000053031362250633600210250ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp 6tIDATxڿq#(:p 5ȯR,&2$F LGI2GHR89y<~~ϹAYF        $ɜ*X3S mx\KYcyQbv9n^rȸ/i,5[rx'!ә07q6 p_=i[tegjU$X& MIf%"AJPHP H P(Ani7AJP%(@MP &(@ vH P(Ani7AJP%(@MP &(@ vH P(Ani7AJP%(@MP &(@  &(@%@j:k 5ɘgA:܇ĭ)yVޠ)F @,iyF F  7?7`E܀5L?_e< X\57`=W2i k:m"LY>ϭC%AAAAAA        4AAAAAAAA        "@ "@ "@ "@ "@ "@ "@ Csc#t p6;A>NsN[\ю6)Gʼ MjgʉRGNl%yQr߄ %m֕˯Wq6w-y`dw6=3i#3,ʤϾ|,}pO? 8lAu:B "@D @D @D @D @D @D @D @}a0IENDB`RcloneBrowser-1.8.0/src/images/mega.png000066400000000000000000000102221362250633600177640ustar00rootroot00000000000000PNG  IHDR@@gm!bKGD̿ pHYs  tIME 6\0v#IDATxitUߐ&@,@@ EaP,D@P-E9*6.Pj(kFP&jTQR0$QAaO'g{>!B!B!B!B!B!B!^eᗸ0S1EXXNFsD>n`@+d,dqF,@<͔KQK,ZI4QJ+hDyҧ3PS42+ԍMJ&|UIؑ2% l<qrS.cF= 6ѐhKl,ږ7 `3O3թYœ.x4^j,+DZ%NGb30KOZf̦|@982t ر Y8Hgl\XF q<~ϱ<~𷟃~h A+PR;;/xF{Sn9;_׳pbо(gqd baK[YTay'.R#~)0%~gSh6u,̏2e Qx=+CA\,վjXz 8ExdD.ܟG4jcQ,c<˛k,w^λ~l5X,OS AVl\往 Y&oy^,Y2,O)ǥn "H`nP'?X9ү; ,C-$lB;ueAwCfWb4]XކZNbNm4t+C,AeS(f O)Zp!/m腞'Ho܀wS09'wOrdp>ko?qRՃ2B$0cmEDeX(\cGzwRGW,mgY 8]ݟIW?n6_-=9iW)`!(8&X~vیO hꠂj+3/{ Ϯq~ZX-Tq: G?(Xv GeWg 7ͰсvvJdW5eX^x@Ŋ-`rȦ/a hOAp^P@; 4uCR@ ƴ5pjӯs,O[|0h ƴN}k(Ӽz¾7ɞ _;\opKP1k WƽYbP1#K O \fB@= uAz6(וּ  >gh$ګ`. Nm+\P F]S\=C[+hZ?7,>8ʴMSм~n9: RwY+V?n 4OaRfY@[&ssG@ 37-`r V?Q 6sE6>xSЮ~a0m犀lRA"V$XF60) NWC$X\)X٘ZT_&wE,\+x8J+@+$ l.Nu']SU xu)G?6T-`㘫TgBo*/T}TW?T}vJ=YO>?e\tPilW}J<йqW?R}vx%S Vt>KŞ 茂'O7+;PI3CU OuT^ hUJ.`공SxΤLYEd?_U0Ĕ*J6᱀L@?PΞZ@ &lU\@c &lg?owx/OԟPI'[] M}2U$HgM j &u.篦PI'[@- #rSOuA@*U0%$ SoA L,r=>J֎d_#L{R&ɉ_|B]0`W?#`f0eOA i'WR IY +}0)j +6`V0 >[-`sVPWu6m Z*W) t 8+p+UP~R\ ^8`!+^P@ı͏"Saz^ 8a:E?m1 fޥ+)i% X|?6"Qנ0õejP@6fP@ hXR@ Xaƚe/!P@ x\v*,x<@)qclֱ,0})0JsKOUs`S@ xD[Fi豈pqN> *c'R)+z,nOE 5޺3Xp\8tKa,V2P1(ìEBc9NIr,ѳ!x4Ѯ(=Xc5%|B߯9`0K ;ƺ]LL?/Cvb3jr=,3E8_m2fNuAX@3NPYS0"Q&ְ e7!~͒.釨Ж'&p,K@y5e}&|tp ޘ4檀v@ާf y^&ES8No ByO30! ep^qwѹ:c\_?Dxv0 \XpNF8DxEw(pR7xw$7]v;q$sr _#3''JM,Մ97 upEq%0rSi/)`(l%bb6>ܜU6-2j-PUEĨDJ(J6^4J{Sd祈[o~:hE(ͬFGSq.rY{ۻIg|JRzKyԐƠ/gB5IchK($܊Q&O=9& һ;IDATxw, M9@,pTFP `B"+P#,`A9ǸJF%RDi#KK'D73yg~}3s玀`?"@X"@Yb Y,d E,dE,"@e ׸ #1SQXuXoQ=(B6`E8rq+Z2?ESpH؞|;ф| zhFnW\7תL) 8]Bb@(ݙc +* 9CW=ZrqEE!gN%ݗq|1!f%u|}~2x : QJ{3mPWUp젹#ΎPWoc9.tm*+wE*b8vW)CjjE#яb~%4;/>XMK } 0EEYrR;WJV=ƯN;wzK.cFrffVu6(&BlE)`<~ YLD%kh m4 ?$YP2?jgV'YxZCh ~S Y!Xt?T pT<`m;A(p3J|tUYPgkzg8s gE.Zpo{ G1TGP??S, z(p䝵#(5 YPg |6(g@950=AX̗fێ$t p0.&r Pi2۴]xM`YgQ4x `MK`f<`MR \ : >*k( : p|+8 sPM1A1 8 vNpRE\RX2L?UTu'UTud, 0[*Lo?xl{({Z?\6OZK'\xlf(xln,'@[HVR (SIJ 3a`/Mx8ۓx;44v|(y-%:U?96eΗd/مsGZ?2^ hvd^NqX ؋Svnp30j#F=kBH`-G$QG<1Ik[ٕ,7FYx} $_^&a:yjdژ0$`+x.2(0)ep5 QJS`"MJoc8H 3q+)C@|IR 64d.6YsSzn@e!T,Pݮ[=QD) r9uF1HWlc(PӌG;;m+@~r|^h@FX@?) mE|3Em @tħ责u@E䠘b3Gg`>]eԕPDY yǨ{Bck"w?N:@릋zc ۑ[ Tǽo3\oND4C3{6@SA+ z( PCϣ࡯ၼs&ÞU%BmDj3n R%'Lyf Ơ{]1{̒{A71}pCљP~;&X|y+f`N 'a̱hA|8gPB]xcۯ+{Zb0;ٍ6gO|?FLBv,xWh 0)ٸ` 6 cnǕhW,"@X"@Yb#` Y,d E,dE,"@?K;ęVIENDB`RcloneBrowser-1.8.0/src/images/onedrive.png000066400000000000000000000115641362250633600207000ustar00rootroot00000000000000PNG  IHDR@@͐ pHYs.#.#x?vbiTXtXML:com.adobe.xmp Pk IDATx{sg"J,+iv]RѸDƥ"KIm5 &a˚ -Đu+*i ,|<[w9wy3g;> ñ%aZ8% G/]æazXm#&Y4XFnpR4s^“pm8'+ø@4#=İO83.<еwçҖJ Ǥsf?I'U#hNsfxǃ7˯g(V4NRV &}==l쓡 O{ۆ ;aQoÁa XI¬vן©a}[C0ﯹdžGD_/ʯF0_4.45pE@T~{Va{$XI^l\@:)Z#bv^X6@vB-'G0u:tw `wf 1?4(Ijxb Θ0#_z-*82jFhzZ-vk[UpxA#c Ϥc(,s^Ѹ(9GI]W4^eP[tFT=a-[ZR8^ܴ-8ǴO,lf 0'K"k陌wO:[8M jMPMcW;6 (uf*+@-vkXNS~@L /8)H.FxAO >r#=bcgAG(|hlہ`.@mk+sp@c/sPӂ9a0)|M.I Ԭ6 ;OUޢR_anxفB_ϗ)863spxNEՆ{5@  ;(2{װΤDžhYEFL.\-U={D+ @luҵyDT(} +8Ԭ]4n*ACf@h)&veP̮ Kv`P@\&\䀠zp/:Bx' a-_ ;+?;0*ݏʚ"yp0f~MxMJ/S_\ `{ C~ $xr}mjhڍU:,6Ub'G wx@;)9m[4G*ϒpyPzFy.aբ"#30[KN*/-]mN¹K `w8>e[LE},&Т@%z,(Ђc`XP{uhBjxMx:,Mla&g-g,.0*b" a\8`υr pE0#-FnYxWпr`3 JX/b}>,V-렎5tFtp| s;ڟ aŇ?`Rfjh&Zz+|L6k?JLsMqqӣ\1.8kMy5~(le-X.87hkrDgVg [.^,͟v*xD}G;cO;c…pxd6(~6-+`^3kZIarɽž0B-=^Ow'              O@@@@@@@@@@@@@@@@@@@@DPCIENDB`RcloneBrowser-1.8.0/src/images/onedrive_inv.png000066400000000000000000000116361362250633600215540ustar00rootroot00000000000000PNG  IHDR@@͐ pHYs.#.#x?vbiTXtXML:com.adobe.xmp E{ IDATxysqךt[%[ iRDDRR)"Tps'4Y 1d/*iܲ/qޟu9~}H ]‰wZ^/?w^I~'|2Xa ;ϥzpI'LRu,1Uʝ_1 OfjW:pIGTnU^@41oX4õaϰsj5X860kn6oz|1beFႰs\u^=izKf}m/Ys8 pKwü3H8ջ)9gvio,3 ǒhѫAmMrc8_spoA闿GsKKLN2>/(߂Yxޮ4RtGؑFwy\6t!x( ivpn@f.ߦ}(|W;N]':uoa5>nj_z ֈOf;{Sׁl@>,:0mWa׃l ³}ukrA׼i7׆l]vk:^x(,sͤ|˳E]'DMöuk&?!׶=yiuae׍l9{ÇÄM׳zT}Muekef2~.w8?sEױA ϗ?UqmG?H}_z]b]pP<NZa'C}3ո[&1|YԯjP_1^𝾡}ɵ?a3|6opkH oofau^7bpVVL՗_wͨ1bpFf0u_.h VM;Ra Gw]%m ,׃ZƬF'.0f|{G]jq+ U翔e58ugd q?ʷ=Iuin3 Sw0{[ [yxs[UpW4J .f7 /;Q|Xܸ5?|CS:9m;#} yjm륱T}PT}`Trw| vTO?m{(gcsQjwp.rI)(/8ߤZagR~>hq-1凎ퟪwhsJj\߃wy9y$50!]o.^]ἑ)3pt6JŰ+RaglO I喟Xg >ɹ!Iua 5e7Ժ6)t.Hl`~>/=|xs@juOS IJT [18z^8J]>9z찦;xc(.iJ걛>Kn/hMq%cM@Ŕ_eqNp%uwv%pI` 8>\Zo9uU|& `vsBjMgkk:{R{.onC*o sg43wD*êޗ& `vE*]-MywRXmif 1y-u% !TMifuXj\υ 'M NrNI(?c:nIS0=ܒjݔC]w8"?>eTu}6 :ߤZۮQf%_~w&FI8qu4jf6#6IT}Vw6X J_A] msJ}T8?q.}]pTmhX8rJ=wMXMhX2h3UWuG,pܖf_Þ=m@pnO$M`"qݦtIrᇈ/1"Kh>F?fWSHW>'uatz->ZP=ָZT7bd|PF#ĝ -βl.ƅ%J-LfbLp# Ə5<[Ƙ˜(+s}!In^1jqTW~)M|.4;&XvHlJ%D _)"D)S4f#B|*"gIiPnH;Fk,+f/ŀ[RҔKMԵh͒S֜yNUK̭Xu݋Epi(XRJpZSV ƛli-oVci=^ڸIZnּV;u,ܭ{/!# yQFT=35:5ٝf7 ljNf Ɖ@&,h̢SJ_tWܟe(0/*Τ XY$~oGidi2wfm-(=\B\%p?y _, ujWuYHboAl|{@#TB+t/l¦b&պU,7w{s<ޖȷ>߳T3nrπ}n>/4r~~ŇJ=WɓшĿV_u0Vd }l0G1O3Ubf9lJG7'b/y4"AddjOydP*ÏT205䱗SL*MLwӍ~(umN&;g^3(y|MA>v EKMϓ{b F.䲛RI dɁhgOkye/9NCo0xYW CNcsC:=j0faBC0L:]h.7xQ ʯh}*,vU|nu~gXNÌM1պ8tikXJ X|@aX e`IwU.:5᮰7##/F yu^C=pnf%,qM7 ?Djz= 5zg5"W8^&ёo}뫨ɽQDPoE Z8M2okA_hVqU7Sxb-3}`.3+姂Y-ƞpw'''f6B#}{%7dY#c5;0՜y32bjѨVK+'4<$՜W N?/x ə~&D>e(7U;ݮ0TRJks(VO3h-P 0Bv݃E~ 3`TFȇT;0єH{2`qg[czwڀH$^4& 2l_moR*n"t/9}}j X wj[Fҭ Xǽ}fʀNq9Aenmu~ך3:bӕh"rw~BДH r (4t-h#ʕ2P8(ҞWj!ҧ"E7)"gGO S"WYɖ[ q. hZᣳk ьbqn5oaHH1J [)NJ  ? 5Q  5bSsۅgz;Yw^ $-gȀ@$ (nĀybzD`f v vqsbcg* \ XBWjM2Hvf X,|Ql-(@ ͣmP?rD\G)bЕ2m@*x2ERNj SZzgB b{E.mT1eW j@IVr %ޣLZ XuX  ՀRFDy#ˀԀeؕB5< (>+T#'^ t`|[M xBzF3\N.r"vf+1o1u!D]]ԏyqE۷iN3Oձ7 ]KhGf+9`6_ZڪUb4>`)wz5ŗx8i)ζ~gٯ@XnciU@Kz RkJw>C,5b\-ދ]MzBHa8YiVNp]K*aU&x,S*Icqzuj FE gg{[e (q$)DIDžzTx2&X|Am_C2SNuV`eթ.S_Mw0Ӡ=8߃Esu=Rxo]+aQ%L('"^E]-Kx6;q+ їZT"16W`kx5/4e"px̔# $=NU_) !>Ms琁S,Z4c׈.Hq[Jmڕ?>eHNךFPJxho&E8NC}Fšʼn& '}v0OE+X,F\|-RaRiFTK'.#2m4Y`,[\A[ g+c8 KG@A))Gbw!ԩe/cz ]UJwhD~<6tm~ V5E)ONp㷗U|vQL#x84Z҆d8TC޽Lr6at.c Y'f;{3;`'Oe9a5gk@g&)!N:mF Y$wd6:Sֽ\pa-򚬛~!uuke`ু:wn, %UgBlΛћ%uP> DWKBLAV[t%"#Ș0Cɕ~'i5>RYkB]l^o'K^/{ex(򮎬U־D|t`oлM`[z#RR7@o y݊4ݽbmb#W,^ ufȝ-:g.К)E\0;_1- AN8*yَo=&sc1qokǕl\C;˿xN_M"ޥ<`ߢܛ<:ܤP,?96A2S=3`YZ?ظ7heڹ ozj~w\z%]2ޚUL+<퉭t,h䃻(P<ꍝ;aƏvgMd8R0KQ֙o9r.W3p+!(p>OOzӝ5r\a7#~p22H+wIENDB`RcloneBrowser-1.8.0/src/images/s3_inv.png000066400000000000000000000163451362250633600202700ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs.#.#x?viTXtXML:com.adobe.xmp T)yIDATxyW9N%Ҭ4HHI4qCumE4hh[+$%4(TJtpKY{|ag=RZRU J@JT)*RU*PT JT@R)*PRU J@JT)*RU*PT JT@R)*PT JT@R)*PRU J@JT)*RU*PT JT@RETi a0Ŭb;9@&~`=I?шHցUݨ8 ga07Q$d01p.$W>( UݘA΍0&StA0y,e5!29n,3 ԣhQ&bqyDm<8b3;bqR^ :svhѫ}|¯~diIxX|ߑG!/+yu=9BR5x3wqNhk2?އ>|ڋ';rdzz_If• 6yMTbgKv5C1S[\Q4֢kcB|D"Hf\+11#;h}B5Kn 66SZO]@%ю .|C`"R WC`84m,qʱ%"_}$Yw2/ o Kao\t۫YqFeTZ2u oyj̵5y?Re?^F=>sԫv\{ l<+Zs<+jKF~n?HGg\j(ws752 L S-Vuܛdzn^_әt3ހe6'Jv0M]>JG{$Tp [d<᱄(œ}eo ol0KHT.g眖 ,@z2ie=D+Z5MQ7W=prl ?N椳E;S,,0$xp=&ʷ{,}:×h)n?6oO./F,XiISy7,,nm2Z-Bc=WXUyZG(hIdaRk|Т)C=iGjwQ{M:9wS7!emƶ9_11!̞YAll72NEfgˑ #`(SE.4L$XͮZ~Ҍ!b ‚0bEIҚYMh1=J*)n[,=1J##$iI7/LdC!U㣦Lۼx| O*)T"Z\,Li$SaVuBLU"_c6fRQTRzམtSDL2I&miw֒;ulr*S zx hio;}&=B0Sr$]a,),F$u"'Kn:~gWJ^#,xGnfh. U;WaWRPlLvSO7U9n><:6p|ܮKyb;4Nc8(r sg?6чŠU:OyZX_W*5 *gwW@N3zyxlKp.Vt2Ot囸e?K"wiSGq{%r/oXͤ(J,l=xJ\)EYJb Wp̕R* &b( *I]1ECb\n|[S^?E@7^ s Xvfaa IF !\E Q̠+1gQUdC3VFüFCSWbҤ Fe&/:_k'rA"/ÌB_OC,Mj5VD+¬L, e67,60ǸMͤzeX3,CAWQ>g YV':5  +!F0haQ=$% Z VRm}up?5@ύ0{/`CC,ƸIxij"R XM0haQ +qӹ;ZXhlF>9@ r}{c 1&ڟM 3B<r f-,ί(ڌa"|;_`,C%ۙ dCm&``hhaQ㫝Xx>P0F@ "av TJr6թOcZю. `͜hDo&y2yVc2 3f}LЏy=kE-,0Ç45+2[H`Xο=9-,wk]-,*1I]Ɵ^FA?6QsfpF,vhn#.WC-,hShaAx울}vHr$ў}>$b$k`tVl:5Uī9!56%i/c}Cg]/b6{UVXHr;Nhik2%F8?5T ,~e;bT]dI:@g2 ~e@[ Uk]s(לcBn]oز@ \?~̥|K3LX>}}y>vSzSLZ-܎&q.{g ,ПQk\#w>6}^֝A6 |_ԯbX 5Pn kAǹK`vzܔ}u(>nlx{ZU ϢDcwn@_>whsԋ<未/'kDLl,`4[jܱڂX;]!\ك e` ,[D}wp.mA׋o[yOg56ۧ,xr#t|Nc\chMnEchc{$FГh¥ԥQ\B:p/O<}{5 D^(Ok+Lԟd =fi4c :V&DjaOy(oY XnK kևnҹջ3+G)|e1VrTN(3yR2V\&{RP-~S|~2K:Z+KN+ EyoyVkݓ g{bfu\W|=SGfRyO<` 3l 2?p𭤏igx v㬣,?VU=H<{|]t'B{yҗYelnH;g[H7畍i> ,0DtᣂO Es yye^+y34sN|3K&|8ق8'^<|ra+@ZS>؝ y|"'f8904r\N{z2lVMlgA&fV/t&0hAMAPE l` ?sLv6g6}iǕT2e>UZRU J@JT)*RU*PT JT@R)*PRU J@JT)*RU*PT JT@R)*PT JT@R)*PRU J@JT)*RU*PT JT@R%&a(IlLR{vPdEpvf(aQa;ihPDs=|>'RJ=~JnRqBhc4w#ʔRX!B!_FN (᧘#!3c? %9Tp>yx)a+:r!yk{bI} H?V%=L|3<:3<̈~GpK<<^T0q9^@͞1G˸XqK )fhY!$xF*DMϞE?D*@;nձIJZju: xjY a GQ=[#!lEPc+mVVi&t8m**JOK {Yo/ZY,1IZ5+-,DLZ{ k7K},-lQjA>H [7ݾ\eaFXb2h&VؙѪ!ָbxΣH%`4(w04ό=HFVInpBVFm8# hcRѸ $`40Wp0:X{I@==  KPc(}%Ȭ/9Wvqk>%ﬕ%`$)*c:y3%TӏLfJr (lhYB b*ﲁ=,I,"H@!B (P ($PDJ7O1LE SЛgB@#$`̞o5 g6q{8-IIl%#{i. cywB cQ!JRx>7XC` >3X:J!/q0VK@idkHF$`:/q*"I@WP=A _[L{䓀ȷ! o܎|0䌱pZIp哀!O1xJv:#$`Ȩ姘'CĔJ@_c( E쏩~0Tq.I]._%WhX0[8M9(#K=7<yFJD)#JegPQ0=߳*R qow!V A6XͮO9*#]g#=Z ((%n2Ӈ# %`mIuy>$`$y|[0Bpق~_Sc0Ḇ.,#C|ZFua j ʌw‡A Ya$n /C E.#TÉn,r  V;vI= Ӹ`06 (avGFch8%HLJ7T&$HE)i4tSp@3p Pr),2}tc fnc#ncp J~j8 YE j8"fi5O orIb͆ӷhU6Ip .$&1]?RP8; mv1j;V*TIs?lib󧀴cK^&0B:q)QL7TBG%)lHBC.YW"!pJuBW% >oM_ C?T``yx|!]g}S=>S`esRL9M/zJzi'GB[syxTX(Z>?APbl#`w)YLPLlo2u_?*X~Y_0Y롍^tTKvF~g$1@'C]+)2sѐtL+)ᬯHr QZ8ۯ̗(E񆧽>+-l\?Je Z*|[򎔰swƌ*ϽA0<ҧZQpTAɥ}Qfi$u!õ&qٶ {*|I?eXΙ ]%aZѓMxǓln808cp_)Or!& %)C}JJc\"W>* KX-2P943F)SFPM?ƱCw%4F4iRZx) r,_i!?;MttCLpܑikSٱ+E[|N*~S"{.ۮ~z+ǡ80#ﯵ*)ώ xԝ XrE]:A_ѸM^W$Mgݺ 'wkU4b/'͔sDeQq0N>V}惔ru9HS{Ҏ0@Y\tǿMODzmVe$h.I+mO)k8;tVn,I')RWirkL&۳\uѧW-ê$z9T!dM)vܿ)M9lDdqR Ihܜn/1ijŒfYHD$z9n{olۮ6GDԔ鸜9_d9u/=-YWo|W<6Л]k[7yN{z5Ӂw/tmH<ߩL.MϘWGktmoeMttt}ثfz@>,ȣZ{F5v{[~[gT23*b8,6t.Á] `z/NV"+y:2]:^g/f6ҳߜeig'8uXxT`w;%N Oi]cc>f<5};U/ǬhAu73.3ٷEY]?g;a!1XB#ٚ ^k곌hWQb;bl[ѥ40Ez"۶0*t\EbL9zULa0(d1419lǬKAvhc=8vc|IL%FDuTRFPLEQBTӋ>|&Iי9S Z0IDATxmV=3αF13_O-4=*9uT|#_H)ypQEIPχBH$1DB$QDDD|0:}{_gf_{_{k]Z$E'=IJP$%IJP$%IJP$%IJP$%IJP$%IJP$%IJP$%IJP`*-GX8)S>{Z N~) xY l؏̠|8{ƶg.]`zecglr?I+N<"~^*f;z @'Lm;?+b-L>;q'a/ Ќuubԗޖ@g9M3~8v:=T@삟sb?G@J9vҿoUp)_u[p$+0*"ɰ&9vʟ/Kn?,#/HӇJI*q%^HF$f h5dm߁.;_-nM0:})iw]/xAFSDM?=)JJƱkxB##A ot=Ŗ1luHյ_u-:KxEh[oXo.%9Yo_-ⷙ<\n ЮwƂ<]lRhS,5x K- RF8hO774_RhO?ZP=6YshK[x1ƃ;tXXp(3ݣ%Z^nMVo @?f4_9[(-i 0MVApHZqc!ɨE7b#ˌ}gw5O 撃xYfGRY]zSJB=y949x S)J7Ø|ֲJ(m`YF g.`LeIP @(P @(P @I @_TD9]s `c&)у5H+M&Vf'OL&Ml7gݳLf{$p X 0Rfa! Ī14C 4D @GUk}-1 M4^4>t(2/ P"Z-u4"=M>FZ8R vU`0u,I i@>0,1R7~(Dowu$` T0u<@jB;&|e_0RuM]TRq+b.4PK)Uw@YV~ @/,\$rTz`ﭣw)tW!-f}8"|7،V @Ja [LHC-᷶7Wwy,=+}8~H2 nk ;BEz bYNF^Xd@C @/d8یy.l{KJ`7}p$/]:` 0>5»! @d4;hz ntACӨuaQ+7<1}t@ӃSQ O^5ihV Kb+Ь^5-?hVhVs  ?R&g.FhR6#FIxۨ_Zt7 m5]lXhNld9|9?ZB1زaДZCMÞd$UF& װi]{j®ftJքuF-hBY _oBm,,-2~ '~@@?`Zi-|{BSGȎabֆo]VXhyj10/qY z#MhضӾVV0u56!+8Y #ޤ)Cx[-o=Ymhw>Pe)Cz,Q]F^e}l|`8u%cVX+nAwr wK"X*QALDQ]1(oL0jLZDR^YpOmT&,uM9:%N/x6u wr:[hZ>?Y]L67_*2*Vb3}DaNB<"*6Z^:Ԁn{qtg$s ^:2/9.}1t!1OK*{)Fu[:&ۙG>!n06YyK>o.GJgټ׺sLp;v~ RIg.f}\?twreu7܏Oin z=@wë!f~M{,~@ ?gޫ~?9j9ټMUkz/9fr|lde/ v>`SAjwis[pcカxʃ>`d3i#m~/wLf X*yLu̞> b<+^a7_!~L|c^.5Py `Ef#3ɳ19rYU٭.`+SOGyN˨;=PCEټ8+=aH zxӰ'eedZEL5pԦ)';Zx5:|LsJ]1L5/v_G0D cKS %Bª6WĵOi"zpb1Os,4PM9m"jz3] {BÒʙƻLd(%f%Oq^q?QzL黸~gVd|}pMUMpN܋>`;I#B'__ ٗث*M](ZT=X4M:kZUPXaHO3~l~v~Sq =XTr#looOo)~X^ uC \0ji"O{YL VA J~ aIz26k-Ym6s7y3CjqxZF0 6:e"S*|0;C\r<9SٙeS9Խ-4:-MGUa[H ۶oП{hޮQmlsBf2RG]L?f:tX<-O?3Ff)èe40-Y.Gv9%D,8z ia,,dwڂ者q. Uش7+Q"t1b#? 2_;WЂ': ԛqs8>lq9L5H=TQAQJ %PF;=yAT>d ;8x~'9C׍:ّm{ Jb*^<}PHzUG$q량gk箭 l,]ẵ0'cd3lGbXGOnͮJ·ir6B w+DUp!*wsǍ:sc7 O"uZ?eا*x>5 6o"Pzb^?7T05%=6UMZ $ j'H@2ԉqs=ӌij5$f) +܎!}} G#hn]w̢O* H*"~KG>bj.tek\Y>eФJebI-; ;SG#c07k6OYb>0Doj(OdBJP$%IJP$%IJP$%IJP$( @I$( @I$( @I$( @I$(SWIENDB`RcloneBrowser-1.8.0/src/images/sftp.png000066400000000000000000000062451362250633600200410ustar00rootroot00000000000000PNG  IHDR@@gm!zTXtRaw profile type exifxQ 9E$qޠvM6ۙ2IBs(TCR\r(G9Zi]WI= Z9~Ta NhWyۙHyBs~xq~O14.$q]$BT+ a "/ g/_o]mZINd+ ?dD|E\!>7F11r\m*ZF5|}[zqn!n)P|PF#ĝ -ݧŸ𾠤YiiòW.a$c φGceN y\_HcWjҗ2 1Ŧt_[8KԀ) b+!I2Ec6"S9K HrCD28xheÌ T`VJ2c ՠIUYrʚs<ϩjbԲ.\={Z1 %+^JAkU1°&[t˛mX>{uϻ6np5oNK=wKkmHCG6|Q/j'GjLNj@qvD'3D n4Of)%&XX2 $렋ݝܗ[n\0} jV )NHÀo/~< Б!5$iCCPICC profilex=JP?XtT6Bb*dp0!0wB܀;Phaa\s{в0-w!ͪ{Fu:,e+їj,FqJg,̋ }3r*nAlGiQ6~LOsN]j cN2aLBEWsÞԥ P7Lōˁ/m6Zў|~e#o'Jgf1'5,d Mc?ϟLL}QlV mhՔ0 ʏo7z[p j\b[S% Fys|]Eҏ{8x#73G/G nHpBDCܗB]Lؼ4ndv7&=[!ڇ/WUyHYU;I p; q$ a3 54Pgb"!ZLvns}#`W&;l[eҁHv1Y mRKy`Z a9;K1L!ڜLv_ r,9GkaTPe;D-[9c$x q3m[ ˵Rn+º4^2!(ؑm > E\OYA=lL Y⛐ZŹcY6ަ2!I~w4zCX~q liby.hް,ԜĢ, n@<_ZuEvQtXO3_Pym4WH1U +w%7&W8X*eIj Ԩ ‰X*eh2drϔ2ʽy"/`V3}{)g2oPyLJO(5)T.`Ø y~:8슺hfL-šldN#]ZJʈnhߙƲ)Hs2߳jK tfNwT=-Yݴ&j@'(QKYfNb'jiYjz0Ll2 !j"IDATx}̕e(AC 2e^\ڈ+˲Lk-XHAs!%X5z3f)ϯ? Ϲs朇u]<}N# e PPJ @ @((%PPJ @ @((%PPJ @ @((%PPJ @ @((%PPJ @ @((%PPJ @ @((%@ @ @((%PPJ @Ynege +/:denryNUӛ;y =9cT9.yfq{=ٙCO>Xl*@ g>Q~l`#7xEgm $;e>\xX;x_73қ_l 9nng\VS%W^R`߮lⅥˢe[pgSVӹ$#)G8؜Ey-5\S`Dҷ@+Qm_J6].=YX][nJ?BX`O/ /WF`{22fR.ܐo!|{L`uFޟTlk\|v6Tz>e>TE9y%Y`m2l^  1rQ&H#Sӛͥ.LC_\ SE:aê~(X|QySnlmy=kL<ܸ\'϶Ds <)7!L=<XܛUH<YjXmN/pvd[̃Y-v[[#n83ɮn]Vd&B?)#L6m? G [mMj<$y1_j՘dQz27[xm6sgŧa<:*d~R-teA ㏹;_5(3.#22rnfe~nGֆx,clұu`mo8;pwF%>n@wS[T .T FPG##T*bU=@# #T[ FP$4 2AA3 4AuҦB FPG#-AAAA *bh*bx1j1"x#TG#($F*#̱X`Mɓ@s [#Hغ $l39=πe_"`WkT Xxf9V3ĦeT^dl#HwY888F.qXDTGuP3q^Ƒ1ጧż~.>_ULwtIo,Y )r-,bmk9:_se+yDSX?*ʵLc?10q0GQ ]1c;/1A$IZƷ1&'sSx2_U`R6738E(p=IP@k޿>>xv1e< rQ,d'XXݹzSTC%iڗ:[=.'}2!`ձm]fy:1A!iMOm# p"sw闦oW}BgqF x<7żQs8-w 'SG"DK1DPSД"в=3Y8EjSTvO2&E7E_$NU}}л>#8a# Sq8kC Ըgk+8.?JSۭNj{.ɷ:SE?KJ&ESTu^5iYKkkBgw{F hINÕnm4OkAW `}x֝:U8N:w&}n-&,Lc{Ui~5sQZqpp xݠO _^e"/YƢ4?0-]=Z[ug+O:ue6|⓶,6r1ͧ,ci21_x?l3<t|VSC 5llgqo2pko}(0m>(qd:CllZ5q, t m^mEVyU-RTخٕ%9r}۔]w̳E!y|1$ɇ|~;ŋ[ǐ70~.ʝO!1) OCcS8;Bz)cºb1OyC!O?e{ 1~x!O㧀cS,3~bp 1~O!0~b ㌟??u%㧐iҢw)RO;PƐu<CN10cei9Ku)r7(wgƐ;4p}c.M$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Iߤ~ۙ+IENDB`RcloneBrowser-1.8.0/src/images/swift_inv.png000066400000000000000000000100731362250633600210670ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp  IDATxhĘDUWtZ5+?u֍M7: UTTFG+IeҢ Tb)")bH""" a$|򼸻re? h&@Mf4 h&@Mf4 h&@Mf4 h&@Mf4h4 L&@3h4 L&@3h4 L&@3h4 L&@3Mf4 h&@Mf4uVoY̏(5cd)R4yP g[OW5Tz칎չ2=Z Bw<{giZX ؞Y 6Pܼ wW 0 "A^A$(+xHP^A$(+ح AHPW UQڑ `'`> 0D HP6$(W `'A  0 "A><'HPW jHPW S Aw`V:bm279`+y&Oa}e+דx q`t_ymj6^1Ɲ|jαX1.f hqX8G7g ciG>L}R.WܻG6yg?l[A*8ҩe=5xs?*4=~\h]AR;.xUeWlfE%c?/ϭ.CR7>T2I3X6^ ALc9G4=E8V,h#~G+~8GzWή/p|[Vs|8o@KP?ٯg(0\VgrK])ͼi=^%Ngfxgt`| mۋ|Zzg|WUY/NfAoӞYRlP@9RuZ9Wu{gx*[~qiPG{Ӟ q]f.IG3;,*p@nvq_,5=J:{N;fj b> ϲiWK}=Hs3n3g`'\`˨ /K<X bœxsg.#z)629:"T2sW^ȯp;!eo2ۥ"?k}I=O|8f3_J =mLʩ_}NV05_21s%It*3$͗x?2)OKRjfR;u?Mm~CLv'_hnraD?P:vw-YLj~Lg#@wOʛ&?d~zzaONp%X:6W8W <ҵpXBM\?({/U<;X,+_0&ERlXn|&5#]zKk |Ozwe߇m_ƿ|H٤ p\adؾO!̐J^.׮yrɷ22~^4O!-`O^CkN 0d/Oy\C'@(` OyYC'kp "?v 05D~ O S-S[C'kإO!!?Oh5D~v']~ ٢í! Tx pj 9(Va֐+yvfy \5MOqC73ڠYE^Vnj_) eЫe\UQF1CYzOuc}iLbڻFԧM1gRg.-6 ˘P^Ah{Q_K莩jtZhrbUA 8Fsr^_6 pf~Hu%gRyԋ3~Rj iMKGrړ:NzgymH&~S|51烜sp甂ӏu*^^Z72yɪ4+8䶳ro?*ϸQ!U?w8vr*1-``oRӟ NOH1]&~NhlZ`'mvY^s!@l7ӻCCNtgO: .ĥ3W pSܙdwESN{G9g]8DE#|pS#]?~7o s?\׾oX~ %)}toi]'uEI(\s`c59ϯ[βps_P-7_WYD^1_"lܾUHvL~<08u:o(E H W?=GYI8)䧑vtH]^F ~|+m2>Nʼ 5{EɊ F4YSVYvO(7XNV@.g+ @pt9+Y.< Gr_]r _oEkaor!}(~ I(@G99 k@H!O7QwS̯=i+)"pP-u~5q> 4?SR;5A\úx*uY/?95G  gr9\grn$@ A @ $@ A @ $@ A @ $@ A @  H @ A H @ ".NBwi-, tN\BW^gU`q!I8M<-Oq9x\u*etBTs)ԭIENDB`RcloneBrowser-1.8.0/src/images/unknown_inv.png000066400000000000000000000105461362250633600214370ustar00rootroot00000000000000PNG  IHDR@@gm! pHYs  iTXtXML:com.adobe.xmp T IDATxkWOiJU,źuU68 tm*Y,E*SKB(bDb(C(A,BQ$H0"V|~~9s *.4  !$$@@BHH   !$$@@BHH#J_i'jTu_C~]S }ԤTrZqSj:=P׫H0J4EMڦKJuHk46fU'4լZéb}e]Z: p9P5CKI٩zEe # j#٫~fRR]

56jP~)`>6vɷ:G&0*lTfϬogGxRԩ<Ր6wfM:[ڔS#Q7j]}PkPMի0ꐪh-7n@;We].7fu*LUt2C$i;]L4@Oo|ȵI d!zIe9bԠ~$2+~H]X/]//XJ>CP5`p-OuRI֫ǔӤ_,6mI>`qiL5 LGLuiF8d :{ Wd^`yM6j gc~KL;eVtV!e?# i湢ZI0TiՐ`ZX]>V`IJ j50 ZtGv뺽6Mr|>vfaۭ![=LjmIVV`Y&0_SlE}72e.˯f-T|zvX`T%v[Rd m YݣWyT~V햅V.xʯzۂ+Hoo]x)NKy+IWTC ClX*^[EgcWF{_?8ZjdkKJ})vc0Vs't^{dy }/ ?pO^;cxOƖRV;|hSynu=f$[v?fw6ӏ^φۦ7ǩdy!ITFg໨+ޓ+և_A{<,-Zi||?RK@_1<~RW t_\P@*Lw5Y\39;!k6-a=&kKVw_XjfrBL*MՅyB!ozBhg^v޷/ *!w N-t7ľ?B-|6w>@w].k5貪@SO@ |tY:@[ւ/t_w;6; =8XW~ `8N&aéʼ7p u5St;ӵ&O?ԥ |ԜHH   !$$M]t-\Ui1adf<%O Y¼twuq*u꨼ NX w pB`B趮.@ @ @ @ @ @ @ @ @ @ H@ @ @ @ T<r |lq p BXV9BX[. *c'}j`Ne)ӯZJZ@@BHH   !$$@@BHH   !$?7TjkIENDB`RcloneBrowser-1.8.0/src/images/yandex.png000066400000000000000000000052631362250633600203540ustar00rootroot00000000000000PNG  IHDR@@gm! zIDATxkUMnƭ. 1ֵ֠j[׵nڍiFeLe9hqb8 BeȆT"TAQPJQ AYs_|s>s>9'k&k:gl*ގVϋQ#9[g=~Qv[_Mٵ\)m0Rm =iǃIRk?G(jwgA\l`tAiKG#@ͱH#?35o) (ٖ#4ة#@ڝ(# ,4> o#Ώs vޱeDo#h$;jxlMsi$i3N4'F߉Tһ`?KQnGR0+Fh<֚Ҭj)NKjR2CYJLz7d3{͙MM%˯3({E?@>wON)\0JeoZ 0x$ fxK)~4?NΗU'}_LF$W@{B_5`>|Dp1Zry?;}ZɛO ,n |f kX [/ Dp,㜬B`'A7f5.%@z[MCp  l%@R|L=UO`.A \ \Kp5_l ' \)>> :k( ܔ݊W3xIA Cp5GWn%@Z.pzA!@,p%VA  \Mp Wj WoET FA N&oO :%LpȞ'@$3Rqo5d^X==A#W$Hq[0M'O\$~t,H%֝E Ffi* VVr&$t`*K~mLu V좽acTOm`~zYd69EV eq>^6۞dmh'e`h%@Tc{~8 h=̈o!@b_ |*Z_0¯DEI{en ^̵f_mjm#@1Fi*g@܆o ߀g:Wl%}ɞ}c&햜V$ohD$@|j5O%@D4DrJkeDruK' }I~G~2q_]I~~:>I~_jC7K9|,/oXhD_s]GH;$cUMAuPtCH%Q~k $}YWZ`!&W= |Q~=Yp諹 }$:-LCHI<X(mKHQ~o&"yEU7`ጰAQ©0{O:k$w`F?X< Q~`,=r<"QwC `>w]x cl|6$ooJ`N{` ~!~_MmFXY~gR#U' T$(w#{^g%w Y 0:ͲX#"{QKY#|&`T& g7eݲZD2nd65F~.`s`$&D2L''(^Y~~8&obƳ`Zm|z \]w;L`Z z" @$Ex5`ڄ}?`& :0Pu`& 2"Z. p׵` Z08S yGo7 a~_xG*̯b9X#k"w? SŜQf](D?*G+#a~9GAyOz`uJۙ`>a~VGH}onG49\!XKCHb4\#,i~Wru~I|2$@_OnI4E}'@$# pAWJ0^iG}~Gr} y` IDATxqq^SKvq^ ce+ؒ1I1qxpD)d1P% PBBD%bl}ߟ||yxH$ @"H$ @"H$ @"H$ @"ZIVk3`s{Z3~ڪK|kTJc+7E/?]Z`Vǽ>3]?Y7AZj}{& f„N7YLN+_.E9 &>RFؠiH`zuQ }X'U\pnMDHuk .GZ;Z^3m-_5Ky0S~B7Yn{JOSJ+3l;-i̮?ִg ZbZCK/j!RQc/*33r†oP+5U3_٬ImNy0m68f|:*93hᒯ0:LܟӰ`Q/3ji3ȓ/E?~G* p\0n[u L,mk Cm9M &o F_`XW:h o cY-V$[nS0 a7{&k`=k@v oOmV)0 'i5p CQ0U`h Ck-Xk6d$U`h =o8 U`h ;8\_60Y/Js u?ZN07| 1`h C[c 0K6>@ 0W  C0M^L;V cފ; @$0DZa`XS,a@ix 0?[CXN Úa8 kF0g9 Zj C06& y`X,1J=`X-晎:Laax@&c&0OM1ODa. CzF!c&Ośi8`ވ| o|Z0 VhuXg c[eo OqICuT[țd'xQ*cl*}fՂRNJYOqIKK9;#13*s@֨qR5!3Gf:it0Ef[U帢Y+j4M֤wtEyּ:@ƽG #5@~[0n=YVqEq=s`3'u{Z2W'A0n`5Ow1s`67 5`ƫC ,Z }ҳ>!qk-H~=?[SHO~ ~*IC4DX]VXN($ ~{>ոQ`toYЧm;6lxM`÷ 3`i7Nx߹-mZkillLǿ1`2[<ȺuӖl777k[]QX ,`zoi>lT0mXx=gc&~ 5B7Lgƴ5ؐowr k3`#z}fl@O5Ee~/av̾L1-mw2?fh Q u]0h?m6GfX%֦C6-u`bw|=b80Ö,}̮6~ @~>Q|Y+mT2_f76~h̨lΫ,ַݕlmvU8of@kq}ҙ0߱_;\g namespace { static void advanceSpinner(QString &text) { int spinnerPos = (int)((size_t)text.length() - 2); QChar current = text[spinnerPos]; static const QChar spinner[] = {'-', '\\', '|', '/'}; size_t spinnerCount = sizeof(spinner) / sizeof(*spinner); const QChar *found = std::find(spinner, spinner + spinnerCount, current); size_t idx = found - spinner; size_t next = idx == spinnerCount - 1 ? 0 : idx + 1; text[spinnerPos] = spinner[next]; } QString getNiceSize(quint64 size) { static const char prefix[] = " KMGTPE"; for (int i = sizeof(prefix) - 2; i >= 0; i--) { quint64 base = quint64(1) << (i * 10); if (size >= 10 * base) { return QString("%1 %2").arg(size / base).arg(QChar(prefix[i])).trimmed(); } } return "0"; } } // namespace class ItemSorter { public: inline ItemSorter(int column, Qt::SortOrder order) : mColumn(column), mOrder(order) { mCompare.setNumericMode(true); } bool operator()(const Item *a, const Item *b) const { switch (mColumn) { case 0: if (a->isFolder != b->isFolder) { return a->isFolder; } return mOrder == Qt::AscendingOrder ? mCompare.compare(a->name, b->name) < 0 : mCompare.compare(b->name, a->name) < 0; case 1: if (a->isFolder != b->isFolder) { return a->isFolder; } if (a->size == b->size) { return mOrder == Qt::AscendingOrder ? mCompare.compare(a->name, b->name) < 0 : mCompare.compare(b->name, a->name) < 0; } return mOrder == Qt::AscendingOrder ? a->size < b->size : b->size < a->size; case 2: if (a->isFolder != b->isFolder) { return a->isFolder; } if (a->modified == b->modified) { return mOrder == Qt::AscendingOrder ? mCompare.compare(a->name, b->name) < 0 : mCompare.compare(b->name, a->name) < 0; } return mOrder == Qt::AscendingOrder ? a->modified < b->modified : b->modified < a->modified; } Q_ASSERT(false); return false; } private: QCollator mCompare; int mColumn; Qt::SortOrder mOrder; }; ItemModel::ItemModel(IconCache *icons, const QString &remote, QObject *parent) : QAbstractItemModel(parent), mRemote(remote), mFixedFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)), mRegExpFolder( R"(^[\d-]+ (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) \s*[\d-]+ (.+)$)"), mRegExpFile(R"(^(\d+) (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\.\d+ (.+)$)") { QStyle *style = qApp->style(); mDriveIcon = style->standardIcon(QStyle::SP_DriveNetIcon); mFolderIcon = style->standardIcon(QStyle::SP_DirIcon); mFileIcon = style->standardIcon(QStyle::SP_FileIcon); auto settings = GetSettings(); mFolderIcons = settings->value("Settings/showFolderIcons", true).toBool(); mFileIcons = settings->value("Settings/showFileIcons", true).toBool(); mRoot = new Item(); mRoot->isFolder = true; mRoot->state = Item::Ready; QObject::connect(this, &ItemModel::getIcon, icons, &IconCache::getIcon); QObject::connect( icons, &IconCache::iconReady, this, [=](Item *item, const QPersistentModelIndex &parent, const QIcon &icon) { item->state = Item::Ready; QString ext = QFileInfo(item->name).suffix(); if (!mLoadedIcons.contains(ext)) { mLoadedIcons.insert(ext, icon); } if (item->isDeleted) { delete item; return; } QModelIndex idx = index(item->num(), 0, parent); emit dataChanged(idx, idx, QVector{Qt::DecorationRole}); }); } ItemModel::~ItemModel() { delete mRoot; } const QDir &ItemModel::path(const QModelIndex &index) const { return get(index)->path; } bool ItemModel::isLoading(const QModelIndex &index) const { return get(index)->parent->isLoading(); } void ItemModel::refresh(const QModelIndex &index) { Item *item = get(index); Item *folderItem = item->isFolder ? item : item->parent; if (folderItem->isLoading()) { return; } load(item->isFolder ? index : index.parent(), folderItem); } void ItemModel::rename(const QModelIndex &index, const QString &name) { Item *item = get(index); item->name = name; item->path.setPath(item->parent->path.filePath(item->name)); emit dataChanged(index, index, QVector{Qt::DisplayRole}); } bool ItemModel::isTopLevel(const QModelIndex &index) const { return get(index)->parent == mRoot; } bool ItemModel::isFolder(const QModelIndex &index) const { return get(index)->isFolder; } QModelIndex ItemModel::addRoot(const QString &name, const QString &path) { emit layoutAboutToBeChanged(); Item *item = new Item(); item->isFolder = true; item->name = name; item->path.setPath(path); item->parent = mRoot; mRoot->childs.append(item); emit layoutChanged(); return createIndex(item->num(), 0, item); } QModelIndex ItemModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } Item *item = get(parent); return createIndex(row, column, item->childs[row]); } QModelIndex ItemModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } Item *child = get(index); if (child->parent == mRoot) { return QModelIndex(); } return createIndex(child->parent->num(), 0, child->parent); } bool ItemModel::hasChildren(const QModelIndex &parent) const { Item *item = get(parent); if (item->isFolder) { if (item->state == Item::Ready) { return !item->childs.isEmpty(); } return true; } return false; } int ItemModel::rowCount(const QModelIndex &parent) const { Item *item = get(parent); if (item->isFolder) { if (item->state == Item::Unknown) { const_cast(this)->load(parent, item); } } return item->childs.count(); } int ItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 3; } void ItemModel::sort(int column, Qt::SortOrder order) { mSortColumn = column; mSortOrder = order; sort(QModelIndex(), mRoot); } QVariant ItemModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const Item *item = get(index); if (role == Qt::DecorationRole && index.column() == 0) { if (item->state == Item::Special) { return QIcon(); } if (item->isFolder) { if (mFolderIcons) { return item->parent == mRoot ? mDriveIcon : mFolderIcon; } return QIcon(); } if (mFileIcons) { QString ext = QFileInfo(item->name).suffix(); auto it = mLoadedIcons.find(ext); if (it == mLoadedIcons.end()) { return mFileIcon; } return it.value(); } return QIcon(); } if (role == Qt::TextAlignmentRole) { if (index.column() == 1) { return Qt::AlignRight + Qt::AlignVCenter; } return QVariant(); } if (role == Qt::DisplayRole) { switch (index.column()) { case 0: return item->name; case 1: if (item->isFolder || item->state == Item::Special) { return QString(); } else { return getNiceSize(item->size); } case 2: return item->modified; } Q_ASSERT(false); } return QVariant(); } QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return "Name"; case 1: return "Size"; case 2: return "Modified"; } } return QVariant(); } bool ItemModel::removeRows(int row, int count, const QModelIndex &parent) { if (!hasIndex(row, 0, parent)) { return false; } Item *item = get(parent); if (row + count > item->childs.count()) { return false; } emit beginRemoveRows(parent, row, row + count - 1); for (int i = row; i < row + count; i++) { Item *node = item->childs.at(i); if (node->isLoading() || node->state == Item::LoadingIcon) { node->isDeleted = true; } else { delete node; } } item->childs.remove(row, count); emit endRemoveRows(); return true; } Qt::ItemFlags ItemModel::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); if (!index.isValid()) { return defaultFlags; } return Qt::ItemIsDropEnabled | defaultFlags; } bool ItemModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { Q_UNUSED(row); Q_UNUSED(column); Q_UNUSED(parent); if (action != Qt::CopyAction && action != Qt::MoveAction) { return false; } if (!data->hasUrls()) { return false; } auto urls = data->urls(); if (urls.count() == 1) { return urls.front().isLocalFile(); } return false; } bool ItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!canDropMimeData(data, action, row, column, parent)) { return false; } QDir path = QDir(data->urls().front().toLocalFile()); Item *item = get(parent); emit drop(path, item->isFolder ? parent : parent.parent()); return false; } Item *ItemModel::get(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : mRoot; } void ItemModel::load(const QPersistentModelIndex &parentIndex, Item *parent) { auto lsd = new QProcess(this); auto lsl = new QProcess(this); auto cache = new QVector(); Item *loading = new Item(); loading->state = Item::Special; loading->name = "... loading [-]"; loading->parent = parent; QTimer *timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, this, [=]() { advanceSpinner(loading->name); auto loadingIndex = createIndex(loading->num(), 0, loading); emit dataChanged(loadingIndex, loadingIndex, QVector{Qt::DisplayRole}); }); auto rcloneFinished = [=]() { sender()->deleteLater(); parent->state = parent->state == Item::Loading1 ? Item::Loading2 : Item::Ready; if (parent->state != Item::Ready) { return; } timer->stop(); timer->deleteLater(); if (parent->isDeleted) { qDeleteAll(*cache); delete cache; delete parent; return; } QHash existing; for (int i = 0; i < parent->childs.count(); i++) { if (parent->childs[i] != loading) { existing.insert(parent->childs[i]->name, i); } } QVector todo; bool modified = false; for (auto &item : *cache) { auto it = existing.find(item->name); if (it == existing.end()) { item->path.setPath(parent->path.filePath(item->name)); if (!item->isFolder && mFileIcons) { QString ext = QFileInfo(item->name).suffix(); if (!mLoadedIcons.contains(ext)) { item->state = Item::LoadingIcon; emit getIcon(item, parentIndex); } } todo.append(item); item = nullptr; } else { Item *old = parent->childs[it.value()]; if (old->isFolder != item->isFolder || old->modified != item->modified || old->size != item->size) { old->state = Item::Unknown; old->isFolder = item->isFolder; old->modified = item->modified; old->size = item->size; modified = true; emit dataChanged(createIndex(it.value(), 0, parent), createIndex(it.value(), 2, parent), QVector{Qt::DisplayRole}); } existing.erase(it); } } qDeleteAll(*cache); delete cache; for (int i = 0; i < parent->childs.count(); i++) { if (parent->childs[i] == loading || existing.contains(parent->childs[i]->name)) { emit beginRemoveRows(parentIndex, i, i); delete parent->childs.takeAt(i); emit endRemoveRows(); i--; } } if (!todo.isEmpty()) { modified = true; emit beginInsertRows(parentIndex, parent->childs.count(), parent->childs.count() + todo.count() - 1); parent->childs += todo; emit endInsertRows(); } if (modified) { sort(parentIndex, parent); } }; QObject::connect(lsd, static_cast( &QProcess::finished), this, rcloneFinished); QObject::connect(lsl, static_cast( &QProcess::finished), this, rcloneFinished); QObject::connect(lsd, &QProcess::readyRead, this, [=]() { while (lsd->canReadLine()) { if (mRegExpFolder.exactMatch(lsd->readLine().trimmed())) { QStringList cap = mRegExpFolder.capturedTexts(); Item *child = new Item(); child->isFolder = true; child->parent = parent; child->name = cap[2]; child->modified = cap[1]; cache->append(child); } } }); QObject::connect(lsl, &QProcess::readyRead, this, [=]() { while (lsl->canReadLine()) { if (mRegExpFile.exactMatch(lsl->readLine().trimmed())) { QStringList cap = mRegExpFile.capturedTexts(); Item *child = new Item(); child->parent = parent; child->name = cap[3]; child->modified = cap[2]; child->size = cap[1].toULongLong(); cache->append(child); } } }); parent->state = Item::Loading1; emit beginInsertRows(parentIndex, 0, 0); parent->childs.prepend(loading); emit endInsertRows(); timer->start(100); UseRclonePassword(lsd); UseRclonePassword(lsl); lsd->start(GetRclone(), QStringList() << "lsd" << GetRcloneConf() << GetDriveSharedWithMe() << GetShowHidden() << GetDefaultRcloneOptionsList() << mRemote + ":" + parent->path.path(), QIODevice::ReadOnly); lsl->start(GetRclone(), QStringList() << "lsl" << GetRcloneConf() << GetDriveSharedWithMe() << GetShowHidden() << "--max-depth" << "1" << GetDefaultRcloneOptionsList() << mRemote + ":" + parent->path.path(), QIODevice::ReadOnly); } void ItemModel::sortRecursive(Item *item, const ItemSorter &sorter) { std::sort(item->childs.begin(), item->childs.end(), sorter); for (auto child : item->childs) { sortRecursive(child, sorter); } } void ItemModel::sort(const QModelIndex &parent, Item *item) { if (item->childs.isEmpty()) { return; } QList parents; parents << parent; emit layoutAboutToBeChanged(parents, QAbstractItemModel::VerticalSortHint); QModelIndexList oldList = persistentIndexList(); QVector> oldNodes; oldNodes.reserve(oldList.count()); for (const auto &index : oldList) { oldNodes.append(qMakePair(get(index), index.column())); } ItemSorter sorter(mSortColumn, mSortOrder); sortRecursive(item, sorter); QModelIndexList newList; newList.reserve(oldNodes.size()); for (const auto &node : oldNodes) { Item *child = node.first; int column = node.second; int row = child->num(); newList.append(createIndex(row, column, child)); } changePersistentIndexList(oldList, newList); emit layoutChanged(parents, QAbstractItemModel::VerticalSortHint); } RcloneBrowser-1.8.0/src/item_model.h000066400000000000000000000056261362250633600174030ustar00rootroot00000000000000#pragma once #include "pch.h" struct Item { Item() {} ~Item() { for (auto child : childs) { if (child->isLoading() || state == LoadingIcon) { child->isDeleted = true; } else { delete child; } } } bool isLoading() const { return state == Loading1 || state == Loading2; } int num() const { Q_ASSERT(parent); return parent->childs.indexOf(const_cast(this)); } Item *parent = nullptr; enum State { Unknown, Loading1, Loading2, Ready, Special, LoadingIcon }; State state = Unknown; bool isFolder = false; bool isDeleted = false; QString name; QDir path; QString modified; quint64 size = 0; QVector childs; }; class IconCache; class ItemSorter; class ItemModel : public QAbstractItemModel { Q_OBJECT public: ItemModel(IconCache *icons, const QString &remote, QObject *parent); ~ItemModel(); const QDir &path(const QModelIndex &index) const; bool isLoading(const QModelIndex &index) const; void refresh(const QModelIndex &index); void rename(const QModelIndex &index, const QString &name); bool isTopLevel(const QModelIndex &index) const; bool isFolder(const QModelIndex &index) const; QModelIndex addRoot(const QString &name, const QString &path); QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &index) const override; bool hasChildren(const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; void sort(int column, Qt::SortOrder order) override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool removeRows(int row, int count, const QModelIndex &parent) override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; signals: void getIcon(Item *item, const QPersistentModelIndex &index); void drop(const QDir &path, const QModelIndex &parent); private: Item *mRoot; QString mRemote; QHash mLoadedIcons; bool mFolderIcons; bool mFileIcons; QIcon mDriveIcon; QIcon mFolderIcon; QIcon mFileIcon; QFont mFixedFont; int mSortColumn; Qt::SortOrder mSortOrder; QRegExp mRegExpFolder; QRegExp mRegExpFile; Item *get(const QModelIndex &index) const; void load(const QPersistentModelIndex &parentIndex, Item *parent); void sortRecursive(Item *item, const ItemSorter &sorter); void sort(const QModelIndex &parent, Item *item); }; RcloneBrowser-1.8.0/src/job_options.cpp000066400000000000000000000066201362250633600201400ustar00rootroot00000000000000#include "job_options.h" #include "utils.h" #include #include #ifdef _WIN32 #pragma warning(disable : 4505) #endif JobOptions::JobOptions(bool isDownload) : JobOptions() { setJobType(isDownload); uniqueId = QUuid::createUuid(); } JobOptions::JobOptions() : jobType(UnknownJobType), operation(UnknownOp), dryRun(false), sync(false), syncTiming(UnknownTiming), skipNewer(false), skipExisting(false), compare(false), compareOption(), verbose(false), sameFilesystem(false), dontUpdateModified(false), maxDepth(0), deleteExcluded(false), isFolder(false) {} const qint32 JobOptions::classVersion = 3; JobOptions::~JobOptions() {} /* * Turn the options held here into a string list for * use in the rclone command. * * This logic was originally in transfer_dialog.cpp. * * This needs to change whenever e.g. new options are * added to the dialog. */ QStringList JobOptions::getOptions() const { QStringList list; if (operation == Copy) { list << "copy"; } else if (operation == Move) { list << "move"; } else if (operation == Sync) { list << "sync"; } if (dryRun) { list << "--dry-run"; } if (sync) { switch (syncTiming) { case During: list << "--delete-during"; break; case After: list << "--delete-after"; break; case Before: list << "--delete-before"; break; default: break; ; } } if (skipNewer) { list << "--update"; } if (skipExisting) { list << "--ignore-existing"; } if (compare) { switch (compareOption) { case Checksum: list << "--checksum"; break; case IgnoreSize: list << "--ignore-size"; break; case SizeOnly: list << "--size-only"; break; case ChecksumIgnoreSize: list << "--checksum" << "--ignore-size"; break; default: break; } } // always verbose list << "--verbose"; if (sameFilesystem) { list << "--one-file-system"; } if (dontUpdateModified) { list << "--no-update-modtime"; } list << "--transfers" << transfers; list << "--checkers" << checkers; if (!bandwidth.isEmpty()) { list << "--bwlimit" << bandwidth; } if (!minSize.isEmpty()) { list << "--min-size" << minSize; } if (!minAge.isEmpty()) { list << "--min-age" << minAge; } if (!maxAge.isEmpty()) { list << "--max-age" << maxAge; } if (maxDepth != 0) { list << "--max-depth" << QString::number(maxDepth); } list << "--contimeout" << (connectTimeout + "s"); list << "--timeout" << (idleTimeout + "s"); list << "--retries" << retries; list << "--low-level-retries" << lowLevelRetries; if (deleteExcluded) { list << "--delete-excluded"; } if (!excluded.isEmpty()) { for (auto line : excluded.split('\n')) { list << "--exclude" << line; } } if (!extra.isEmpty()) { for (auto arg : extra.split(' ')) { list << arg; } } if (DriveSharedWithMe) { list << "--drive-shared-with-me"; } list << "--stats" << "1s"; list << "--stats-file-name-length" << "0"; QStringList defaultRcloneOptionsList = GetDefaultRcloneOptionsList(); if (!defaultRcloneOptionsList.isEmpty()) { list << defaultRcloneOptionsList; } list << source; list << dest; return list; } SerializationException::SerializationException(QString msg) : QException(), Message(msg) {} RcloneBrowser-1.8.0/src/job_options.h000066400000000000000000000046641362250633600176130ustar00rootroot00000000000000#pragma once #include #include #include class JobOptions { public: explicit JobOptions(bool isDownload); JobOptions(); ~JobOptions(); enum Operation { UnknownOp, Copy, Move, Sync }; enum JobType { UnknownJobType, Upload, Download }; /* * The following enums have their int values synchronized with the * list indexes on the gui. Changes needed to be synchronized. */ enum SyncTiming { During, After, Before, UnknownTiming }; enum CompareOption { SizeAndModTime, Checksum, IgnoreSize, SizeOnly, ChecksumIgnoreSize }; QString description; JobType jobType; Operation operation; bool dryRun; // not persisted bool sync; SyncTiming syncTiming; bool skipNewer; bool skipExisting; bool compare; CompareOption compareOption; bool verbose; bool sameFilesystem; bool dontUpdateModified; QString transfers; QString checkers; QString bandwidth; QString minSize; QString minAge; QString maxAge; int maxDepth; QString connectTimeout; QString idleTimeout; QString retries; QString lowLevelRetries; bool deleteExcluded; QString excluded; QString extra; QString source; QString dest; bool isFolder; QUuid uniqueId; bool DriveSharedWithMe; void setJobType(bool isDownload) { jobType = (isDownload) ? Download : Upload; } QString myName() const { return "JobOptions"; // this->staticQtMetaObject.myName(); } QStringList getOptions() const; bool operator==(const JobOptions &other) const { return uniqueId == other.uniqueId; } /* * This allows the de-serialization method to accomodate changes * to the class structure, especially (most easily) added members. * * Increment the value each time a change is made, emit the new field(s) * in the operator<< function, and in operator>> add conditional logic * based on the version for reading in the new field(s) */ static const qint32 classVersion; }; class JobOptionsListWidgetItem : public QListWidgetItem { public: JobOptionsListWidgetItem(JobOptions *jo, const QIcon &icon, const QString &text) : QListWidgetItem(icon, text), mJobData(jo) {} void SetData(JobOptions *jo) { mJobData = jo; } JobOptions *GetData() { return mJobData; } private: JobOptions *mJobData; }; class SerializationException : public QException { public: QString Message; explicit SerializationException(QString msg); }; RcloneBrowser-1.8.0/src/job_widget.cpp000066400000000000000000000202171362250633600177260ustar00rootroot00000000000000#include "job_widget.h" #include "utils.h" JobWidget::JobWidget(QProcess *process, const QString &info, const QStringList &args, const QString &source, const QString &dest, QWidget *parent) : QWidget(parent), mProcess(process) { ui.setupUi(this); mArgs.append(QDir::toNativeSeparators(GetRclone())); mArgs.append(GetRcloneConf()); mArgs.append(args); ui.source->setText(source); ui.dest->setText(dest); ui.info->setText(info); ui.details->setVisible(false); ui.output->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui.output->setVisible(false); QObject::connect( ui.showDetails, &QToolButton::toggled, this, [=](bool checked) { ui.details->setVisible(checked); ui.showDetails->setArrowType(checked ? Qt::DownArrow : Qt::RightArrow); }); QObject::connect( ui.showOutput, &QToolButton::toggled, this, [=](bool checked) { ui.output->setVisible(checked); ui.showOutput->setArrowType(checked ? Qt::DownArrow : Qt::RightArrow); }); ui.cancel->setIcon( QApplication::style()->standardIcon(QStyle::SP_DialogCloseButton)); QObject::connect(ui.cancel, &QToolButton::clicked, this, [=]() { if (mRunning) { int button = QMessageBox::question( this, "Transfer", QString("rclone process is still running. Do you want to cancel it?"), QMessageBox::Yes | QMessageBox::No); if (button == QMessageBox::Yes) { cancel(); } } else { emit closed(); } }); ui.copy->setIcon( QApplication::style()->standardIcon(QStyle::SP_FileLinkIcon)); QObject::connect(ui.copy, &QToolButton::clicked, this, [=]() { QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText(mArgs.join(" ")); }); QObject::connect(mProcess, &QProcess::readyRead, this, [=]() { QRegExp rxSize( R"(^Transferred:\s+(\S+ \S+) \(([^)]+)\)$)"); // Until rclone 1.42 QRegExp rxSize2( R"(^Transferred:\s+([0-9.]+)(\S)? \/ (\S+) (\S+), ([0-9%-]+), (\S+ \S+), (\S+) (\S+)$)"); // Starting with rclone 1.43 QRegExp rxErrors(R"(^Errors:\s+(\S+)$)"); QRegExp rxChecks(R"(^Checks:\s+(\S+)$)"); // Until rclone 1.42 QRegExp rxChecks2( R"(^Checks:\s+(\S+) \/ (\S+), ([0-9%-]+)$)"); // Starting with // rclone 1.43 QRegExp rxTransferred(R"(^Transferred:\s+(\S+)$)"); // Until rclone 1.42 QRegExp rxTransferred2( R"(^Transferred:\s+(\S+) \/ (\S+), ([0-9%-]+)$)"); // Starting with // rclone 1.43 QRegExp rxTime(R"(^Elapsed time:\s+(\S+)$)"); QRegExp rxProgress( R"(^\*([^:]+):\s*([^%]+)% done.+(ETA: [^)]+)$)"); // Until rclone 1.38 QRegExp rxProgress2( R"(\*([^:]+):\s*([^%]+)% \/[a-zA-z0-9.]+, [a-zA-z0-9.]+\/s, (\w+)$)"); // Starting with rclone 1.39 while (mProcess->canReadLine()) { QString line = mProcess->readLine().trimmed(); if (++mLines == 10000) { ui.output->clear(); mLines = 1; } ui.output->appendPlainText(line); if (line.isEmpty()) { for (auto it = mActive.begin(), eit = mActive.end(); it != eit; /* empty */) { auto label = it.value(); if (mUpdated.contains(label)) { ++it; } else { it = mActive.erase(it); ui.progress->removeWidget(label->buddy()); ui.progress->removeWidget(label); delete label->buddy(); delete label; } } mUpdated.clear(); continue; } if (rxSize.exactMatch(line)) { ui.size->setText(rxSize.cap(1)); ui.bandwidth->setText(rxSize.cap(2)); } else if (rxSize2.exactMatch(line)) { ui.size->setText(rxSize2.cap(1) + " " + rxSize2.cap(2) + "B" + ", " + rxSize2.cap(5)); ui.bandwidth->setText(rxSize2.cap(6)); ui.eta->setText(rxSize2.cap(8)); ui.totalsize->setText(rxSize2.cap(3) + " " + rxSize2.cap(4)); } else if (rxErrors.exactMatch(line)) { ui.errors->setText(rxErrors.cap(1)); } else if (rxChecks.exactMatch(line)) { ui.checks->setText(rxChecks.cap(1)); } else if (rxChecks2.exactMatch(line)) { ui.checks->setText(rxChecks2.cap(1) + " / " + rxChecks2.cap(2) + ", " + rxChecks2.cap(3)); } else if (rxTransferred.exactMatch(line)) { ui.transferred->setText(rxTransferred.cap(1)); } else if (rxTransferred2.exactMatch(line)) { ui.transferred->setText(rxTransferred2.cap(1) + " / " + rxTransferred2.cap(2) + ", " + rxTransferred2.cap(3)); } else if (rxTime.exactMatch(line)) { ui.elapsed->setText(rxTime.cap(1)); } else if (rxProgress.exactMatch(line)) { QString name = rxProgress.cap(1).trimmed(); auto it = mActive.find(name); QLabel *label; QProgressBar *bar; if (it == mActive.end()) { label = new QLabel(); label->setText(name); bar = new QProgressBar(); bar->setMinimum(0); bar->setMaximum(100); bar->setTextVisible(true); label->setBuddy(bar); ui.progress->addRow(label, bar); mActive.insert(name, label); } else { label = it.value(); bar = static_cast(label->buddy()); } bar->setValue(rxProgress.cap(2).toInt()); bar->setToolTip(rxProgress.cap(3)); mUpdated.insert(label); } else if (rxProgress2.exactMatch(line)) { QString name = rxProgress2.cap(1).trimmed(); auto it = mActive.find(name); QLabel *label; QProgressBar *bar; if (it == mActive.end()) { label = new QLabel(); QString nameTrimmed; if (name.length() > 47) { nameTrimmed = name.left(25) + "..." + name.right(19); } else { nameTrimmed = name; } label->setText(nameTrimmed); bar = new QProgressBar(); bar->setMinimum(0); bar->setMaximum(100); bar->setTextVisible(true); label->setBuddy(bar); ui.progress->addRow(label, bar); mActive.insert(name, label); } else { label = it.value(); bar = static_cast(label->buddy()); } bar->setValue(rxProgress2.cap(2).toInt()); bar->setToolTip("File name: " + name + "\nFile stats" + rxProgress2.cap(0).mid(rxProgress2.cap(0).indexOf(':'))); mUpdated.insert(label); } } }); QObject::connect(mProcess, static_cast( &QProcess::finished), this, [=](int status, QProcess::ExitStatus) { mProcess->deleteLater(); for (auto label : mActive) { ui.progress->removeWidget(label->buddy()); ui.progress->removeWidget(label); delete label->buddy(); delete label; } mRunning = false; if (status == 0) { ui.showDetails->setStyleSheet( "QToolButton { border: 0; color: black; }"); ui.showDetails->setText("Finished"); } else { ui.showDetails->setStyleSheet( "QToolButton { border: 0; color: red; }"); ui.showDetails->setText("Error"); } ui.cancel->setToolTip("Close"); emit finished(ui.info->text()); }); ui.showDetails->setStyleSheet("QToolButton { border: 0; color: green; }"); ui.showDetails->setText("Running"); } JobWidget::~JobWidget() {} void JobWidget::showDetails() { ui.showDetails->setChecked(true); } void JobWidget::cancel() { if (!mRunning) { return; } mProcess->kill(); mProcess->waitForFinished(); emit closed(); } RcloneBrowser-1.8.0/src/job_widget.h000066400000000000000000000011361362250633600173720ustar00rootroot00000000000000#pragma once #include "pch.h" #include "ui_job_widget.h" class JobWidget : public QWidget { Q_OBJECT public: JobWidget(QProcess *process, const QString &info, const QStringList &args, const QString &source, const QString &dest, QWidget *parent = nullptr); ~JobWidget(); void showDetails(); public slots: void cancel(); signals: void finished(const QString &info); void closed(); private: Ui::JobWidget ui; bool mRunning = true; QProcess *mProcess; int mLines = 0; QStringList mArgs; QHash mActive; QSet mUpdated; }; RcloneBrowser-1.8.0/src/job_widget.ui000066400000000000000000000331261362250633600175640ustar00rootroot00000000000000 JobWidget 0 0 966 449 Form 0 0 0 0 0 QToolButton { border: 0 } true Qt::ToolButtonTextBesideIcon Qt::RightArrow Copy command to clipboard QToolButton { border: 0 } Cancel QToolButton { border: 0 } 0 0 0 Qt::Horizontal 40 20 Total size: totalsize 0 0 140 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Remaining time: eta 0 0 140 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Errors: errors 0 0 140 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Checks: checks 0 0 200 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Elapsed time: elapsed 0 0 140 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true true 0 0 Size: size true QToolButton { border: 0 } Show Output true Qt::ToolButtonTextBesideIcon Qt::RightArrow Bandwidth: bandwidth Transfers: transferred QPlainTextEdit::NoWrap true 0 0 140 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true 0 0 Source: source 0 0 Destination: dest 0 0 140 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true 0 0 200 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true QFormLayout::ExpandingFieldsGrow Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop showDetails cancel source dest size elapsed bandwidth showOutput output RcloneBrowser-1.8.0/src/list_of_job_options.cpp000066400000000000000000000142601362250633600216560ustar00rootroot00000000000000#include "list_of_job_options.h" #include #include #include #include #include static QDataStream &operator>>(QDataStream &dataStream, JobOptions &jo); static QDataStream &operator<<(QDataStream &dataStream, JobOptions &jo); static QDataStream &operator>>(QDataStream &in, JobOptions::Operation &e); static QDataStream &operator>>(QDataStream &in, JobOptions::SyncTiming &e); static QDataStream &operator>>(QDataStream &in, JobOptions::CompareOption &e); static QDataStream &operator>>(QDataStream &in, JobOptions::JobType &e); ListOfJobOptions *ListOfJobOptions::SavedJobOptions = nullptr; const QString ListOfJobOptions::persistenceFileName = "tasks.bin"; ListOfJobOptions::ListOfJobOptions() {} ListOfJobOptions *ListOfJobOptions::getInstance() { if (SavedJobOptions == nullptr) { SavedJobOptions = new ListOfJobOptions(); RestoreFromUserData(*SavedJobOptions); } return SavedJobOptions; } bool ListOfJobOptions::Persist(JobOptions *jo) { bool isNew = !this->tasks.contains(jo); if (isNew) this->tasks.append(jo); else { // int ix = tasks.indexOf(jo); // JobOptions *old = tasks[ix]; // qDebug() << QString("old [%1] New [%2]") // .arg(old->description) // .arg(jo->description); } PersistToUserData(); return isNew; } bool ListOfJobOptions::Forget(JobOptions *jo) { bool isKnown = this->tasks.contains(jo); if (!isKnown) return false; int ix = tasks.indexOf(jo); tasks.removeAt(ix); // qDebug() << QString("removed [%1]").arg(jo->description); PersistToUserData(); return isKnown; } QFile *ListOfJobOptions::GetPersistenceFile(QIODevice::OpenModeFlag mode) { QDir outputDir; if (IsPortableMode()) { // in portable mode tasks' file will be saved in the same folder as // excecutable #ifdef Q_OS_MACOS // on macOS excecutable file is located in // ./rclone-browser.app/Contents/MasOS/ // to get actual bundle folder we have // to traverse three levels up outputDir = QDir(qApp->applicationDirPath() + "/../../.."); #else #ifdef Q_OS_WIN // not macOS outputDir = QDir(qApp->applicationDirPath()); #else QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); outputDir = QDir(xdg_config_home + "/rclone-browser"); #endif #endif } else { // get data location folder from Qt - OS dependend outputDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } if (!outputDir.exists()) { outputDir.mkpath("."); } QString filePath = outputDir.absoluteFilePath(persistenceFileName); QFile *file = new QFile(filePath); if (!file->open(mode)) { // qDebug() << QString("Could not open ") << file->fileName(); delete file; file = nullptr; } return file; } bool ListOfJobOptions::RestoreFromUserData(ListOfJobOptions &dataIn) { QFile *file = GetPersistenceFile(QIODevice::ReadOnly); if (file == nullptr) return false; QDataStream instream(file); instream.setVersion(QDataStream::Qt_5_2); while (!instream.atEnd()) { try { JobOptions *jo = new JobOptions(); instream >> *jo; dataIn.tasks.append(jo); } catch (SerializationException &ex) { // qDebug() << QString("failed to restore tasks: ") << ex.Message; file->close(); delete file; return false; } } file->close(); delete file; return true; } bool ListOfJobOptions::PersistToUserData() { QFile *file = GetPersistenceFile( QIODevice::WriteOnly); // note this mode implies Truncate also if (file == nullptr) return false; QDataStream outstream(file); outstream.setVersion(QDataStream::Qt_5_2); for (JobOptions *it : tasks) { outstream << *it; } file->flush(); file->close(); emit tasksListUpdated(); delete file; return true; } QDataStream &operator<<(QDataStream &stream, JobOptions &jo) { stream << jo.myName() << JobOptions::classVersion << jo.description << jo.jobType << jo.operation << /* jo.dryRun <<*/ jo.sync << jo.syncTiming << jo.skipNewer << jo.skipExisting << jo.compare << jo.compareOption << jo.verbose << jo.sameFilesystem << jo.dontUpdateModified << jo.transfers << jo.checkers << jo.bandwidth << jo.minSize << jo.minAge << jo.maxAge << jo.maxDepth << jo.connectTimeout << jo.idleTimeout << jo.retries << jo.lowLevelRetries << jo.deleteExcluded << jo.excluded << jo.extra << jo.DriveSharedWithMe << jo.source << jo.dest << jo.isFolder << jo.uniqueId; return stream; } QDataStream &operator>>(QDataStream &stream, JobOptions &jo) { QString actualName; qint32 actualVersion; stream >> actualName; if (QString::compare(actualName, jo.myName()) != 0) throw SerializationException("incorrect class"); stream >> actualVersion; if (actualVersion > JobOptions::classVersion) throw SerializationException("stored version is newer"); stream >> jo.description >> jo.jobType >> jo.operation >> /* jo.dryRun >> */ jo.sync >> jo.syncTiming >> jo.skipNewer >> jo.skipExisting >> jo.compare >> jo.compareOption >> jo.verbose >> jo.sameFilesystem >> jo.dontUpdateModified >> jo.transfers >> jo.checkers >> jo.bandwidth >> jo.minSize >> jo.minAge >> jo.maxAge >> jo.maxDepth >> jo.connectTimeout >> jo.idleTimeout >> jo.retries >> jo.lowLevelRetries >> jo.deleteExcluded >> jo.excluded >> jo.extra >> jo.DriveSharedWithMe >> jo.source >> jo.dest; // as fields are added in later revisions, check actualVersion here and // conditionally extract any new fields iff they are expected based on the // stream value if (actualVersion >= 2) { stream >> jo.isFolder; if (actualVersion >= 3) { stream >> jo.uniqueId; } } return stream; } QDataStream &operator>>(QDataStream &in, JobOptions::Operation &e) { in >> (quint32 &)e; return in; } QDataStream &operator>>(QDataStream &in, JobOptions::SyncTiming &e) { in >> (quint32 &)e; return in; } QDataStream &operator>>(QDataStream &in, JobOptions::CompareOption &e) { in >> (quint32 &)e; return in; } QDataStream &operator>>(QDataStream &in, JobOptions::JobType &e) { in >> (quint32 &)e; return in; } RcloneBrowser-1.8.0/src/list_of_job_options.h000066400000000000000000000012331362250633600213170ustar00rootroot00000000000000#pragma once #include "job_options.h" #include class ListOfJobOptions : public QObject { Q_OBJECT protected: ~ListOfJobOptions() = default; ListOfJobOptions(); public: static ListOfJobOptions *getInstance(); bool Persist(JobOptions *jo); bool Forget(JobOptions *jo); QList &getTasks() { return tasks; } signals: void tasksListUpdated(); private: static ListOfJobOptions *SavedJobOptions; static const QString persistenceFileName; static bool RestoreFromUserData(ListOfJobOptions &dataIn); static QFile *GetPersistenceFile(QIODevice::OpenModeFlag mode); QList tasks; bool PersistToUserData(); }; RcloneBrowser-1.8.0/src/main.cpp000066400000000000000000000153671362250633600165470ustar00rootroot00000000000000#include "main_window.h" #include "utils.h" int main(int argc, char *argv[]) { #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) static const char ENV_VAR_QT_DEVICE_PIXEL_RATIO[] = "QT_DEVICE_PIXEL_RATIO"; if (!qEnvironmentVariableIsSet(ENV_VAR_QT_DEVICE_PIXEL_RATIO) && !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR") && !qEnvironmentVariableIsSet("QT_SCALE_FACTOR") && !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #endif QApplication app(argc, argv); // app.setApplicationDisplayName("Rclone Browser"); app.setApplicationName("rclone-browser"); app.setOrganizationName("rclone-browser"); app.setWindowIcon(QIcon(":/icons/icon.png")); // initialize SSL libraries // see: https://github.com/linuxdeploy/linuxdeploy-plugin-qt/issues/57 #if defined(Q_OS_LINUX) QString currentDir = QDir::currentPath(); QDir::setCurrent(QCoreApplication::applicationDirPath()); QSslSocket::supportsSsl(); QDir::setCurrent(currentDir); #endif auto settings = GetSettings(); // initialize proxy settings if (!(settings->contains("Settings/useProxy"))) { settings->setValue("Settings/useProxy", "false"); }; if (!(settings->contains("Settings/http_proxy"))) { settings->setValue("Settings/http_proxy", ""); }; if (!(settings->contains("Settings/https_proxy"))) { settings->setValue("Settings/https_proxy", ""); }; if (!(settings->contains("Settings/no_proxy"))) { settings->setValue("Settings/no_proxy", ""); }; if (settings->value("Settings/useProxy").toBool()) { qputenv("HTTP_PROXY", settings->value("Settings/http_proxy").toByteArray()); qputenv("http_proxy", settings->value("Settings/http_proxy").toByteArray()); qputenv("HTTPS_PROXY", settings->value("Settings/https_proxy").toByteArray()); qputenv("https_proxy", settings->value("Settings/https_proxy").toByteArray()); qputenv("NO_PROXY", settings->value("Settings/no_proxy").toByteArray()); qputenv("no_proxy", settings->value("Settings/no_proxy").toByteArray()); } // remmber darkMode state on app startup // during first run the darkModeIni key might not exist if (!(settings->contains("Settings/darkModeIni"))) { // if darkModeIni does not exist create new key settings->setValue("Settings/darkModeIni", "false"); }; // during first run the darkMode key might not exist if (!(settings->contains("Settings/darkMode"))) { // if darkMode does not exist create new key settings->setValue("Settings/darkMode", "false"); }; bool darkMode = settings->value("Settings/darkMode").toBool(); settings->setValue("Settings/darkModeIni", darkMode); // during first run the iconSize key might not exist if (!(settings->contains("Settings/iconSize"))) { // if iconSize does not exist create new key settings->setValue("Settings/iconSize", "medium"); }; // enforce one instance of Rclone Browser per user QString tmpDir; QString applicationNameBase; QFileInfo applicationPath; QFileInfo applicationUserPath; // QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); // qDebug() << QString("main.cpp $XDG_CONFIG_HOME: " + xdg_config_home); // QString APPIMAGE = qgetenv("APPIMAGE"); // qDebug() << QString("main.cpp $APPIMAGE: " + APPIMAGE); QFileInfo appBundlePath; if (IsPortableMode()) { // qDebug() << QString("isPortable is true"); // applicationPath = qApp->applicationFilePath(); #ifdef Q_OS_MACOS // on macOS excecutable file is located in // ./rclone-browser.app/Contents/MasOS/ // to get actual bundle folder we have // to traverse three levels up applicationPath = qApp->applicationFilePath(); tmpDir = applicationPath.absolutePath() + "/../../.."; // get bundle name QFileInfo MacOSPath = applicationPath.dir().path(); QFileInfo ContentsPath = MacOSPath.dir().path(); appBundlePath = ContentsPath.dir().path(); #else // not macOS #ifdef Q_OS_WIN applicationPath = qApp->applicationFilePath(); tmpDir = applicationPath.absolutePath(); #else QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); tmpDir = xdg_config_home + "/rclone-browser"; // create ./rclone-browser folder if (!QDir(tmpDir).exists()) { QDir().mkdir(tmpDir); } #endif #endif } else { // not portable mode // get tmp folder from Qt - OS dependend tmpDir = QDir::tempPath(); } // check if tmpDir writable // as isWritable does weird things on Windows // we do this old fashioned way by creating temp file QTemporaryFile tempfile(tmpDir + "/rclone-browserXXXXXX.test"); if (tempfile.open()) { tempfile.close(); tempfile.remove(); } else { // folder has no write access if (IsPortableMode()) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setText("You need write " "access to this folder:\n\n" #ifdef Q_OS_MACOS + appBundlePath.absolutePath() + #else #ifdef Q_OS_WIN + tmpDir + #else + tmpDir.left(tmpDir.length() - 15) + #endif #endif "\n\n" #ifdef Q_OS_MACOS "Or remove file:\n\n" + appBundlePath.baseName() + ".ini \n\nfrom the above folder " #else #ifdef Q_OS_WIN "Or remove file:\n\n" + applicationPath.baseName() + ".ini \n\nfrom the above folder " #else "Or remove folder:\n\n" + tmpDir.left(tmpDir.length() - 15) + "\n\n" #endif #endif "to disable portable mode."); msgBox.exec(); } else { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setText("You need write " "access to this folder: \n\n" #ifdef Q_OS_MACOS + tmpDir #else #ifdef Q_OS_WIN + tmpDir #else + tmpDir.left(tmpDir.length() - 15) #endif #endif ); msgBox.exec(); } return static_cast( 0x80004004); // exit immediately if folder not writable } // qDebug() << QString("main.cpp tmpDir: " + tmpDir); // not most elegant as fixed name but in reality not big deal QLockFile lockFile(tmpDir + "/.RcloneBrowser_4q6RgLs2RpbJA.lock"); if (!lockFile.tryLock(100)) { // if already running display warning and quit QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setText("Rclone Browser is already running." "\r\n\nOnly one instance is allowed."); msgBox.exec(); return static_cast( 0x80004004); // exit immediately if another instance is running } MainWindow w; w.show(); return app.exec(); } RcloneBrowser-1.8.0/src/main_window.cpp000066400000000000000000001275241362250633600201350ustar00rootroot00000000000000#include "main_window.h" #include "job_options.h" #include "job_widget.h" #include "list_of_job_options.h" #include "mount_widget.h" #include "preferences_dialog.h" #include "remote_widget.h" #include "stream_widget.h" #include "transfer_dialog.h" #include "utils.h" #ifdef Q_OS_MACOS #include "osx_helper.h" #endif MainWindow::MainWindow() { ui.setupUi(this); if (IsPortableMode()) { this->setWindowTitle("Rclone Browser - portable mode"); } else { this->setWindowTitle("Rclone Browser"); } #if defined(Q_OS_WIN) // disable "?" WindowContextHelpButton QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif #if !defined(Q_OS_MACOS) auto settings = GetSettings(); bool darkMode = settings->value("Settings/darkMode").toBool(); // enable dark mode for Windows and Linux if (darkMode) { qApp->setStyle(QStyleFactory::create("Fusion")); QPalette darkPalette; darkPalette.setColor(QPalette::Window, QColor(53, 53, 53)); darkPalette.setColor(QPalette::WindowText, Qt::white); darkPalette.setColor(QPalette::Base, QColor(25, 25, 25)); darkPalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); darkPalette.setColor(QPalette::ToolTipBase, Qt::white); darkPalette.setColor(QPalette::ToolTipText, Qt::white); darkPalette.setColor(QPalette::Text, Qt::white); darkPalette.setColor(QPalette::Button, QColor(53, 53, 53)); darkPalette.setColor(QPalette::ButtonText, Qt::white); darkPalette.setColor(QPalette::BrightText, Qt::red); darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); darkPalette.setColor(QPalette::HighlightedText, Qt::black); qApp->setPalette(darkPalette); qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; " "border: 1px solid white; }"); } #else // enable dark mode for older macOS QString sysInfo = QSysInfo::productVersion(); if (sysInfo == "10.9" || sysInfo == "10.10" || sysInfo == "10.11" || sysInfo == "10.12" || sysInfo == "10.13") { auto settings = GetSettings(); bool darkMode = settings->value("Settings/darkMode").toBool(); if (darkMode) { qApp->setStyle(QStyleFactory::create("Fusion")); QPalette darkPalette; darkPalette.setColor(QPalette::Window, QColor(53, 53, 53)); darkPalette.setColor(QPalette::WindowText, Qt::white); darkPalette.setColor(QPalette::Base, QColor(25, 25, 25)); darkPalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); darkPalette.setColor(QPalette::ToolTipBase, Qt::white); darkPalette.setColor(QPalette::ToolTipText, Qt::white); darkPalette.setColor(QPalette::Text, Qt::white); darkPalette.setColor(QPalette::Button, QColor(53, 53, 53)); darkPalette.setColor(QPalette::ButtonText, Qt::white); darkPalette.setColor(QPalette::BrightText, Qt::red); darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); darkPalette.setColor(QPalette::HighlightedText, Qt::black); qApp->setPalette(darkPalette); qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: " "#2a82da; border: 1px solid white; }"); } } #endif mSystemTray.setIcon(qApp->windowIcon()); { auto settings = GetSettings(); if (settings->contains("MainWindow/geometry")) { restoreGeometry(settings->value("MainWindow/geometry").toByteArray()); } SetRclone(settings->value("Settings/rclone").toString()); SetRcloneConf(settings->value("Settings/rcloneConf").toString()); mAlwaysShowInTray = settings->value("Settings/alwaysShowInTray", false).toBool(); mCloseToTray = settings->value("Settings/closeToTray", false).toBool(); mNotifyFinishedTransfers = settings->value("Settings/notifyFinishedTransfers", true).toBool(); mSystemTray.setVisible(mAlwaysShowInTray); // during first run the lastUsed keys might not exist if (!(settings->contains("Settings/lastUsedSourceFolder"))) { // if lastUsedSourceFolder does not exist create new empty key settings->setValue("Settings/lastUsedSourceFolder", ""); }; if (!(settings->contains("Settings/lastUsedDestFolder"))) { // if lastUsedDestFolder does not exist create new empty key settings->setValue("Settings/lastUsedDestFolder", ""); }; if (!(settings->contains("Settings/defaultDownloadOptions"))) { // if defaultDownloadOptions does not exist create new empty key settings->setValue("Settings/defaultDownloadOptions", ""); }; #ifdef Q_OS_MACOS // for macOS by default exclude .DS_Store files from uploads if (!(settings->contains("Settings/defaultUploadOptions"))) { // if defaultDownloadOptions does not exist create new empty key settings->setValue("Settings/defaultUploadOptions", "--exclude .DS_Store"); }; #else if (!(settings->contains("Settings/defaultUploadOptions"))) { // if defaultDownloadOptions does not exist create new empty key settings->setValue("Settings/defaultUploadOptions", ""); }; #endif if (!(settings->contains("Settings/defaultRcloneOptions"))) { // if defaultRcloneOptions does not exist create new empty key settings->setValue("Settings/defaultRcloneOptions", "--fast-list"); }; } QObject::connect(ui.preferences, &QAction::triggered, this, [=]() { PreferencesDialog dialog(this); if (dialog.exec() == QDialog::Accepted) { auto settings = GetSettings(); settings->setValue("Settings/rclone", dialog.getRclone().trimmed()); settings->setValue("Settings/rcloneConf", dialog.getRcloneConf().trimmed()); settings->setValue("Settings/stream", dialog.getStream()); settings->setValue("Settings/mount", dialog.getMount()); settings->setValue("Settings/defaultDownloadDir", dialog.getDefaultDownloadDir().trimmed()); settings->setValue("Settings/defaultUploadDir", dialog.getDefaultUploadDir().trimmed()); settings->setValue("Settings/defaultDownloadOptions", dialog.getDefaultDownloadOptions().trimmed()); settings->setValue("Settings/defaultUploadOptions", dialog.getDefaultUploadOptions().trimmed()); settings->setValue("Settings/defaultRcloneOptions", dialog.getDefaultRcloneOptions().trimmed()); settings->setValue("Settings/checkRcloneBrowserUpdates", dialog.getCheckRcloneBrowserUpdates()); settings->setValue("Settings/checkRcloneUpdates", dialog.getCheckRcloneUpdates()); settings->setValue("Settings/alwaysShowInTray", dialog.getAlwaysShowInTray()); settings->setValue("Settings/closeToTray", dialog.getCloseToTray()); settings->setValue("Settings/notifyFinishedTransfers", dialog.getNotifyFinishedTransfers()); settings->setValue("Settings/showFolderIcons", dialog.getShowFolderIcons()); settings->setValue("Settings/showFileIcons", dialog.getShowFileIcons()); settings->setValue("Settings/rowColors", dialog.getRowColors()); settings->setValue("Settings/showHidden", dialog.getShowHidden()); settings->setValue("Settings/darkMode", dialog.getDarkMode()); settings->setValue("Settings/iconSize", dialog.getIconSize().trimmed()); settings->setValue("Settings/useProxy", dialog.getUseProxy()); settings->setValue("Settings/http_proxy", dialog.getHttpProxy().trimmed()); settings->setValue("Settings/https_proxy", dialog.getHttpsProxy().trimmed()); settings->setValue("Settings/no_proxy", dialog.getNoProxy().trimmed()); SetRclone(dialog.getRclone()); SetRcloneConf(dialog.getRcloneConf()); mFirstTime = true; rcloneGetVersion(); mAlwaysShowInTray = dialog.getAlwaysShowInTray(); mCloseToTray = dialog.getCloseToTray(); mNotifyFinishedTransfers = dialog.getNotifyFinishedTransfers(); mSystemTray.setVisible(mAlwaysShowInTray); } }); QObject::connect(ui.quit, &QAction::triggered, this, [=]() { mCloseToTray = false; close(); }); QObject::connect(ui.about, &QAction::triggered, this, [=]() { QMessageBox::about( this, "Rclone Browser", QString( R"(

GUI for rclone, v)" RCLONE_BROWSER_VERSION "

" R"(

Copyright © 2019

)" R"(

Current development and maintenance
kapitainsky

)" R"(

New features and fixes
contributors

)" R"(

Original version
Martins Mozeiko

)")); }); QObject::connect(ui.aboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); QObject::connect( ui.remotes, &QListWidget::currentItemChanged, this, [=](QListWidgetItem *current) { ui.open->setEnabled(current != NULL); }); QObject::connect(ui.remotes, &QListWidget::itemActivated, ui.open, &QPushButton::clicked); QObject::connect(ui.config, &QPushButton::clicked, this, &MainWindow::rcloneConfig); QObject::connect(ui.refresh, &QPushButton::clicked, this, &MainWindow::rcloneListRemotes); QObject::connect(ui.open, &QPushButton::clicked, this, [=]() { auto item = ui.remotes->selectedItems().front(); QString type = item->data(Qt::UserRole).toString(); QString name = item->text(); bool isLocal = type == "local"; bool isGoogle = type == "drive"; auto remote = new RemoteWidget(&mIcons, name, isLocal, isGoogle, ui.tabs); QObject::connect(remote, &RemoteWidget::addMount, this, &MainWindow::addMount); QObject::connect(remote, &RemoteWidget::addStream, this, &MainWindow::addStream); QObject::connect(remote, &RemoteWidget::addTransfer, this, &MainWindow::addTransfer); int index = ui.tabs->addTab(remote, name); ui.tabs->setCurrentIndex(index); }); QObject::connect(ui.tabs, &QTabWidget::tabCloseRequested, ui.tabs, &QTabWidget::removeTab); QObject::connect(ui.tasksListWidget, &QListWidget::currentItemChanged, this, [=](QListWidgetItem *current) { ui.buttonDeleteTask->setEnabled(current != nullptr); ui.buttonEditTask->setEnabled(current != nullptr); ui.buttonRunTask->setEnabled(current != nullptr); ui.buttonDryrunTask->setEnabled(current != nullptr); }); QObject::connect(ui.buttonRunTask, &QPushButton::clicked, this, [=]() { JobOptionsListWidgetItem *item = static_cast( ui.tasksListWidget->currentItem()); runItem(item); }); QObject::connect(ui.buttonDryrunTask, &QPushButton::clicked, this, [=]() { JobOptionsListWidgetItem *item = static_cast( ui.tasksListWidget->currentItem()); runItem(item, true); }); // QObject::connect(ui.tasksListWidget, &QListWidget::itemDoubleClicked, // this, [=]() // { // editSelectedTask(); // }); QObject::connect(ui.buttonEditTask, &QPushButton::clicked, this, [=]() { editSelectedTask(); }); QObject::connect(ui.buttonDeleteTask, &QPushButton::clicked, this, [=]() { JobOptionsListWidgetItem *item = static_cast( ui.tasksListWidget->currentItem()); JobOptions *jo = item->GetData(); ListOfJobOptions::getInstance()->Forget(jo); }); QObject::connect(ListOfJobOptions::getInstance(), &ListOfJobOptions::tasksListUpdated, this, &MainWindow::listTasks); QStyle *style = QApplication::style(); ui.buttonDeleteTask->setIcon(style->standardIcon(QStyle::SP_TrashIcon)); ui.buttonEditTask->setIcon(style->standardIcon(QStyle::SP_FileIcon)); ui.buttonRunTask->setIcon(style->standardIcon(QStyle::SP_CommandLink)); mUploadIcon = style->standardIcon(QStyle::SP_ArrowUp); mDownloadIcon = style->standardIcon(QStyle::SP_ArrowDown); ui.tabs->tabBar()->setTabButton(0, QTabBar::RightSide, nullptr); ui.tabs->tabBar()->setTabButton(0, QTabBar::LeftSide, nullptr); ui.tabs->tabBar()->setTabButton(1, QTabBar::RightSide, nullptr); ui.tabs->tabBar()->setTabButton(1, QTabBar::LeftSide, nullptr); ui.tabs->tabBar()->setTabButton(2, QTabBar::RightSide, nullptr); ui.tabs->tabBar()->setTabButton(2, QTabBar::LeftSide, nullptr); ui.tabs->setCurrentIndex(0); listTasks(); QObject::connect(&mSystemTray, &QSystemTrayIcon::activated, this, [=](QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::DoubleClick || reason == QSystemTrayIcon::Trigger) { showNormal(); mSystemTray.setVisible(mAlwaysShowInTray); #ifdef Q_OS_MACOS osxShowDockIcon(); #endif } }); QObject::connect(&mSystemTray, &QSystemTrayIcon::messageClicked, this, [=]() { showNormal(); mSystemTray.setVisible(mAlwaysShowInTray); #ifdef Q_OS_MACOS osxShowDockIcon(); #endif ui.tabs->setCurrentIndex(1); if (mLastFinished) { mLastFinished->showDetails(); ui.jobsArea->ensureWidgetVisible(mLastFinished); } }); QMenu *trayMenu = new QMenu(this); QObject::connect( trayMenu->addAction("&Show"), &QAction::triggered, this, [=]() { MainWindow::setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); MainWindow::show(); // bring window to top on macOS MainWindow::raise(); // bring window from minimized state on macOS MainWindow::activateWindow(); // bring window to front/unminimize on // windows mSystemTray.setVisible(mAlwaysShowInTray); #ifdef Q_OS_MACOS osxShowDockIcon(); #endif }); QObject::connect(trayMenu->addAction("&Quit"), &QAction::triggered, this, &QWidget::close); mSystemTray.setContextMenu(trayMenu); mStatusMessage = new QLabel(); ui.statusBar->addWidget(mStatusMessage); ui.statusBar->setStyleSheet("QStatusBar::item { border: 0; }"); QTimer::singleShot(0, ui.remotes, SLOT(setFocus())); QString rclone = GetRclone(); if (rclone.isEmpty()) { rclone = QStandardPaths::findExecutable("rclone"); if (rclone.isEmpty()) { QMessageBox::information( this, "Error", "Cannot check rclone version!\nPlease verify rclone location."); emit ui.preferences->trigger(); } else { auto settings = GetSettings(); settings->setValue("Settings/rclone", rclone); SetRclone(rclone); } } else { rcloneGetVersion(); } } MainWindow::~MainWindow() { auto settings = GetSettings(); settings->setValue("MainWindow/geometry", saveGeometry()); } void MainWindow::rcloneGetVersion() { bool firstTime = mFirstTime; mFirstTime = false; QProcess *p = new QProcess(); QObject::connect( p, static_cast( &QProcess::finished), this, [=](int code, QProcess::ExitStatus) { if (code == 0) { QString version = p->readAllStandardOutput().trimmed(); // extract rclone version - numbers only QString rclone_info1 = version; QString rclone_version_no; int lineBreak = rclone_info1.indexOf('\n'); if (lineBreak != -1) { rclone_info1.remove(lineBreak, rclone_info1.length() - lineBreak); rclone_version_no = rclone_info1; rclone_version_no.replace("rclone v", ""); rclone_version_no.replace("-DEV", ""); } else { // for very old rclone versions format was one line only rclone_version_no = rclone_info1.trimmed(); rclone_version_no.replace("rclone v", ""); rclone_version_no.replace("-DEV", ""); } // save current version no in settings auto settings = GetSettings(); settings->setValue("Settings/rcloneVersion", rclone_version_no); #if defined(Q_OS_WIN32) // check if required version unsigned int result = compareVersion(rclone_version_no.toStdString(), "1.50"); if (result == 2) { QMessageBox::warning( this, "", "For mount functionality to work you need " "rclone version at least v1.50 " "and your current version is v" + rclone_version_no + ". Mount will be disabled. \n\nPlease consider upgrading."); }; #endif QStringList lines = version.split("\n", QString::SkipEmptyParts); QString rclone_info2; QString rclone_info3; int counter = 0; foreach (QString line, lines) { line = line.trimmed(); if (counter == 1) rclone_info2 = line.replace("- ", ""); if (counter == 2) rclone_info3 = line.replace("- ", ""); counter++; }; QFileInfo appBundlePath; #ifdef Q_OS_MACOS if (IsPortableMode()) { QFileInfo applicationPath = qApp->applicationFilePath(); QFileInfo MacOSPath = applicationPath.dir().path(); QFileInfo ContentsPath = MacOSPath.dir().path(); appBundlePath = ContentsPath.dir().path(); mStatusMessage->setText( rclone_info1 + " in " + QDir::toNativeSeparators(GetRclone().replace( appBundlePath.fileName() + "/Contents/MacOS/../../../", "")) + ", " + rclone_info2 + ", " + rclone_info3); } else { mStatusMessage->setText(rclone_info1 + " in " + QDir::toNativeSeparators(GetRclone()) + ", " + rclone_info2 + ", " + rclone_info3); } #else #ifdef Q_OS_WIN mStatusMessage->setText(rclone_info1 + " in " + QDir::toNativeSeparators(GetRclone()) + ", " + rclone_info2 + ", " + rclone_info3); #else if (IsPortableMode()) { QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); QString appImageConfigFolder = xdg_config_home.right(xdg_config_home.length()-xdg_config_home.lastIndexOf("/")); mStatusMessage->setText(rclone_info1 + " in " + QDir::toNativeSeparators(GetRclone().replace(appImageConfigFolder + "/..", "")) + ", " + rclone_info2 + ", " + rclone_info3); } else { mStatusMessage->setText(rclone_info1 + " in " + QDir::toNativeSeparators(GetRclone()) + ", " + rclone_info2 + ", " + rclone_info3); } #endif #endif rcloneListRemotes(); } else { if (p->error() != QProcess::FailedToStart) { if (getConfigPassword(p)) { rcloneGetVersion(); } else { close(); } p->deleteLater(); return; } if (firstTime) { if (p->error() == QProcess::FailedToStart) { QMessageBox::information( this, "Error", "Wrong rclone executable or rclone not found!\nPlease select " "its location in next dialog."); } else { QMessageBox::information(this, "Error", "Cannot check rclone version!\nPlease " "verify rclone location."); } emit ui.preferences->trigger(); } } auto settings = GetSettings(); /// check rclone version // get already stored rclone version no QString rclone_version_no = settings->value("Settings/rcloneVersion").toString(); // during first run the key might not exist yet if (!(settings->contains("Settings/checkRcloneUpdates"))) { // if checkRcloneUpdates does not exist create new key settings->setValue("Settings/checkRcloneUpdates", true); }; bool checkRcloneUpdates = settings->value("Settings/checkRcloneUpdates").toBool(); // if check updates enabled in settings if (checkRcloneUpdates) { QString last_check; QString current_date = QDate::currentDate().toString(); if (!(settings->contains("Settings/lastRcloneUpdateCheck"))) { // if lastRcloneUpdateCheck does not exist create new key settings->setValue("Settings/lastRcloneUpdateCheck", current_date); } else { // read last check date last_check = settings->value("Settings/lastRcloneUpdateCheck").toString(); }; // dont check if already checked today (once per day only) if (!(last_check == current_date)) { // remmber when last checked settings->setValue("Settings/lastRcloneUpdateCheck", current_date); QString url = "https://api.github.com/repos/rclone/rclone/releases/latest"; QNetworkAccessManager manager; QNetworkReply *response = manager.get(QNetworkRequest(QUrl(url))); QEventLoop event; connect(response, SIGNAL(finished()), &event, SLOT(quit())); event.exec(); QByteArray content = response->readAll(); QJsonParseError jsonError; QJsonDocument document = QJsonDocument::fromJson( content, &jsonError); // parse and capture the error flag if (jsonError.error == QJsonParseError::NoError) { if (document.object().contains("tag_name")) { QJsonValue tag_name = document.object().value("tag_name"); QString rclone_latest_version_no = tag_name.toString(QString()); rclone_latest_version_no.replace("v", ""); rclone_latest_version_no.replace("-DEV", ""); rclone_latest_version_no = rclone_latest_version_no.trimmed(); // check if new version available and if yes display information unsigned int result = compareVersion(rclone_latest_version_no.toStdString(), rclone_version_no.toStdString()); // latest version is greater than current if (result == 1) { QMessageBox::information( this, "", QString( R"(

New rclone version is available

)" R"(

You have: v)" + rclone_version_no + "
" R"(New version: v)" + rclone_latest_version_no + "

" R"(

Visit rclone downloads page to upgrade

)")); }; }; }; }; }; /// check rclone browser version // during first run the key might not exist yet if (!(settings->contains("Settings/checkRcloneBrowserUpdates"))) { // if checkRcloneBrowserUpdates does not exist create new key settings->setValue("Settings/checkRcloneBrowserUpdates", true); }; bool checkRcloneBrowserUpdates = settings->value("Settings/checkRcloneBrowserUpdates").toBool(); // if check updates enabled in settings if (checkRcloneBrowserUpdates) { QString last_check; QString current_date = QDate::currentDate().toString(); if (!(settings->contains("Settings/lastRcloneBrowserUpdateCheck"))) { // if lastRcloneBrowserUpdateCheck does not exist create new key settings->setValue("Settings/lastRcloneBrowserUpdateCheck", current_date); } else { // read last check date last_check = settings->value("Settings/lastRcloneBrowserUpdateCheck") .toString(); }; // dont check if already checked today (once per day only) if (!(last_check == current_date)) { // remmber when last checked settings->setValue("Settings/lastRcloneBrowserUpdateCheck", current_date); // get latest version available QString url = "https://api.github.com/repos/kapitainsky/" "rclonebrowser/releases/latest"; QNetworkAccessManager manager; QNetworkReply *response = manager.get(QNetworkRequest(QUrl(url))); QEventLoop event; connect(response, SIGNAL(finished()), &event, SLOT(quit())); event.exec(); QByteArray content = response->readAll(); QJsonParseError jsonError; QJsonDocument document = QJsonDocument::fromJson( content, &jsonError); // parse and capture the error flag if (jsonError.error == QJsonParseError::NoError) { if (document.object().contains("tag_name")) { QJsonValue tag_name = document.object().value("tag_name"); QString rclone_browser_latest_version_no = tag_name.toString(QString()); rclone_browser_latest_version_no = rclone_browser_latest_version_no.trimmed(); // check if new version available and if yes display information unsigned int result = compareVersion( rclone_browser_latest_version_no.toStdString(), RCLONE_BROWSER_VERSION); // latest version is greater than current if (result == 1) { QMessageBox::information( this, "", QString( R"(

New Rclone Browser version is available

)" R"(

You have: v)" RCLONE_BROWSER_VERSION "
" R"(New version: v)" + rclone_browser_latest_version_no + "

" R"(

Visit releases page to download

)")); }; }; }; }; }; p->deleteLater(); }); UseRclonePassword(p); p->start(GetRclone(), QStringList() << "version" << "--ask-password=false", QIODevice::ReadOnly); } void MainWindow::rcloneConfig() { // for macOS and Linux we have to take care of possible spaces in rclone and // rclone.conf paths by using "" around them QString terminalRcloneCmd; if (!GetRcloneConf().isEmpty()) { terminalRcloneCmd = "\"" + GetRclone() + "\"" + " config" + " --config " + "\"" + GetRcloneConf().at(1) + "\""; } else { terminalRcloneCmd = "\"" + GetRclone() + "\"" + " config"; } #if defined(Q_OS_WIN32) && (QT_VERSION < QT_VERSION_CHECK(5, 7, 0)) QProcess::startDetached(GetRclone(), QStringList() << "config" << GetRcloneConf()); return; #else QProcess *p = new QProcess(this); QObject::connect(p, static_cast( &QProcess::finished), this, [=](int code, QProcess::ExitStatus) { if (code == 0) { emit rcloneListRemotes(); } p->deleteLater(); }); #endif #if defined(Q_OS_WIN32) #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) p->setCreateProcessArgumentsModifier( [](QProcess::CreateProcessArguments *args) { args->flags |= CREATE_NEW_CONSOLE; args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES; }); p->setProgram(GetRclone()); p->setArguments(QStringList() << "config" << GetRcloneConf()); #endif #elif defined(Q_OS_MACOS) auto tmp = new QFile("/tmp/rclone_config.command"); tmp->open(QIODevice::WriteOnly); QTextStream(tmp) << "#!/bin/sh\n" << terminalRcloneCmd << "\n"; tmp->close(); tmp->setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ExeUser | QFileDevice::ReadGroup | QFileDevice::ExeGroup | QFileDevice::ReadOther | QFileDevice::ExeOther); p->setProgram("open"); p->setArguments(QStringList() << tmp->fileName()); #else QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString terminal = env.value("TERMINAL"); if (terminal.isEmpty()) { terminal = QStandardPaths::findExecutable("gnome-terminal"); if (terminal.isEmpty()) { terminal = QStandardPaths::findExecutable("xfce4-terminal"); if (terminal.isEmpty()) { terminal = QStandardPaths::findExecutable("xterm"); if (terminal.isEmpty()) { terminal = QStandardPaths::findExecutable("x-terminal-emulator"); if (terminal.isEmpty()) { terminal = QStandardPaths::findExecutable("konsole"); if (terminal.isEmpty()) { QMessageBox::critical(this, "Error", "Not sure how to launch terminal!\n" "Please set path to terminal executable in " "$TERMINAL environment variable.", QMessageBox::Ok); return; } } } } } } p->setArguments(QStringList() << "-e" << terminalRcloneCmd); p->setProgram(terminal); #endif #if !defined(Q_OS_WIN32) || (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) UseRclonePassword(p); p->start(QIODevice::NotOpen); #endif } void MainWindow::rcloneListRemotes() { ui.remotes->clear(); QProcess *p = new QProcess(); QObject::connect( p, static_cast( &QProcess::finished), this, [=](int code, QProcess::ExitStatus) { if (code == 0) { QStyle *style = qApp->style(); QString bytes = p->readAllStandardOutput().trimmed(); QStringList items = bytes.split('\n'); auto settings = GetSettings(); bool darkModeIni = settings->value("Settings/darkModeIni").toBool(); QString iconSize = settings->value("Settings/iconSize").toString(); for (const QString &line : items) { if (line.isEmpty()) { continue; } QStringList parts = line.split(':'); if (parts.count() != 2) { continue; } QString name = parts[0].trimmed(); QString type = parts[1].trimmed(); QString tooltip = type; QString img_add = ""; int size; // medium scale by default double darkModeIconScale = 1.333; double lightModeiconScale = 2; // to avoid "variable not used" compiler error if (darkModeIconScale == lightModeiconScale) { }; // set icons scale based on iconSize value if (iconSize == "small") { lightModeiconScale = 1.5; darkModeIconScale = 1; } if (iconSize == "medium") { lightModeiconScale = 2; darkModeIconScale = 1.333; } if (iconSize == "large") { lightModeiconScale = 3; darkModeIconScale = 2; } #if !defined(Q_OS_MACOS) // _inv only for dark mode // we use darkModeIni to apply mode active at startup if (darkModeIni) { img_add = "_inv"; } else { img_add = ""; } #if defined(Q_OS_WIN) // on Windows dark theme changes PM_ListViewIconSize size // so we have to adjust if (darkModeIni) { size = darkModeIconScale * style->pixelMetric(QStyle::PM_ListViewIconSize); } else { size = lightModeiconScale * style->pixelMetric(QStyle::PM_ListViewIconSize); } #else // for Linux/BSD PM_ListViewIconSize stays the same size = lightModeiconScale * style->pixelMetric(QStyle::PM_ListViewIconSize); #endif #else QString sysInfo = QSysInfo::productVersion(); // dark mode on older macOS if (sysInfo == "10.9" || sysInfo == "10.10" || sysInfo == "10.11" || sysInfo == "10.12" || sysInfo == "10.13") { // on older macOS we also have to adjust icon size per mode if (darkModeIni) { size = darkModeIconScale * style->pixelMetric(QStyle::PM_ListViewIconSize); img_add = "_inv"; } else { size = lightModeiconScale * style->pixelMetric(QStyle::PM_ListViewIconSize); img_add = ""; } } else { // for macOS > 10.13 native dark mode does not change IconSize base size = 1.5 * lightModeiconScale * style->pixelMetric(QStyle::PM_ListViewIconSize); } #endif ui.remotes->setIconSize(QSize(size, size)); QString path = ":/remotes/images/" + type.replace(' ', '_') + img_add + ".png"; QIcon icon(QFile(path).exists() ? path : ":/remotes/images/unknown" + img_add + ".png"); QListWidgetItem *item = new QListWidgetItem(icon, name); item->setData(Qt::UserRole, type); item->setToolTip(tooltip); ui.remotes->addItem(item); } } else { if (p->error() != QProcess::FailedToStart) { if (getConfigPassword(p)) { rcloneListRemotes(); } } } p->deleteLater(); }); UseRclonePassword(p); p->start(GetRclone(), QStringList() << "listremotes" << GetRcloneConf() << "--long" << "--ask-password=false", QIODevice::ReadOnly); } bool MainWindow::getConfigPassword(QProcess *p) { QString output = p->readAllStandardError().trimmed(); if (output.indexOf("RCLONE_CONFIG_PASS") > 0) { bool ok; QString password = QInputDialog::getText( this, qApp->applicationDisplayName(), "Enter password for .rclone.conf configuration file:", QLineEdit::Password, QString(), &ok); if (ok) { SetRclonePassword(password); return true; } } else if (output.indexOf("unknown command \"listremotes\"") > 0) { QMessageBox::critical(this, qApp->applicationDisplayName(), "It seems rclone version you are using is too " "old.\nPlease upgrade to the latest version"); return false; } return false; } bool MainWindow::canClose() { if (mJobCount == 0) { return true; } bool wasVisible = isVisible(); ui.tabs->setCurrentIndex(1); showNormal(); int button = QMessageBox::question(this, "Rclone Browser", QString("There are %1 job(s) running.\n" "Do you want to stop them and quit?") .arg(mJobCount), QMessageBox::Yes | QMessageBox::No); if (!wasVisible) { hide(); } if (button == QMessageBox::Yes) { for (int i = 0; i < ui.jobs->count(); i++) { QWidget *widget = ui.jobs->itemAt(i)->widget(); if (auto mount = qobject_cast(widget)) { mount->cancel(); } else if (auto transfer = qobject_cast(widget)) { transfer->cancel(); } else if (auto stream = qobject_cast(widget)) { stream->cancel(); } } return true; } return false; } void MainWindow::closeEvent(QCloseEvent *ev) { if (mCloseToTray && isVisible()) { #ifdef Q_OS_MACOS osxHideDockIcon(); #endif mSystemTray.show(); hide(); ev->ignore(); return; } if (canClose()) { QApplication::quit(); } else { ev->ignore(); } } void MainWindow::listTasks() { ui.tasksListWidget->clear(); ListOfJobOptions *ljo = ListOfJobOptions::getInstance(); for (JobOptions *jo : ljo->getTasks()) { JobOptionsListWidgetItem *item = new JobOptionsListWidgetItem( jo, jo->jobType == JobOptions::JobType::Download ? mDownloadIcon : mUploadIcon, jo->description); ui.tasksListWidget->addItem(item); } } void MainWindow::runItem(JobOptionsListWidgetItem *item, bool dryrun) { if (item == nullptr) return; JobOptions *jo = item->GetData(); jo->dryRun = dryrun; QStringList args = jo->getOptions(); addTransfer(QString("%1 %2").arg(jo->operation).arg(jo->source), jo->source, jo->dest, args); } void MainWindow::editSelectedTask() { auto selection = ui.tasksListWidget->selectionModel()->currentIndex(); JobOptionsListWidgetItem *item = static_cast( ui.tasksListWidget->currentItem()); JobOptions *jo = item->GetData(); bool isDownload = (jo->jobType == JobOptions::Download); QString remote = isDownload ? jo->source : jo->dest; QString path = isDownload ? jo->dest : jo->source; // qDebug() << "remote:" + remote; // qDebug() << "path:" + path; TransferDialog td(isDownload, false, remote, path, jo->isFolder, this, jo, true); td.exec(); // restore the selection to help user keep track of what s/he was doing ui.tasksListWidget->selectionModel()->select(selection, QItemSelectionModel::Select); // edit mode on the TransferDialog suppresses the usual Accept buttons // and the Save Task button closes it... so there is nothing more to do here } void MainWindow::addTransfer(const QString &message, const QString &source, const QString &dest, const QStringList &args) { QProcess *transfer = new QProcess(this); transfer->setProcessChannelMode(QProcess::MergedChannels); auto widget = new JobWidget(transfer, message, args, source, dest); auto line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); QObject::connect( widget, &JobWidget::finished, this, [=](const QString &info) { if (mNotifyFinishedTransfers) { qApp->alert(this); mLastFinished = widget; mSystemTray.showMessage("Transfer finished", info); } if (--mJobCount == 0) { ui.tabs->setTabText(1, "Jobs"); } else { ui.tabs->setTabText(1, QString("Jobs (%1)").arg(mJobCount)); } }); QObject::connect(widget, &JobWidget::closed, this, [=]() { if (widget == mLastFinished) { mLastFinished = nullptr; } ui.jobs->removeWidget(widget); ui.jobs->removeWidget(line); widget->deleteLater(); delete line; if (ui.jobs->count() == 2) { ui.noJobsAvailable->show(); } }); if (ui.jobs->count() == 2) { ui.noJobsAvailable->hide(); } ui.jobs->insertWidget(0, widget); ui.jobs->insertWidget(1, line); ui.tabs->setTabText(1, QString("Jobs (%1)").arg(++mJobCount)); UseRclonePassword(transfer); transfer->start(GetRclone(), GetRcloneConf() + args, QIODevice::ReadOnly); } void MainWindow::addMount(const QString &remote, const QString &folder) { QProcess *mount = new QProcess(this); mount->setProcessChannelMode(QProcess::MergedChannels); auto widget = new MountWidget(mount, remote, folder); auto line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); QObject::connect(widget, &MountWidget::finished, this, [=]() { if (--mJobCount == 0) { ui.tabs->setTabText(1, "Jobs"); } else { ui.tabs->setTabText(1, QString("Jobs (%1)").arg(mJobCount)); } }); QObject::connect(widget, &MountWidget::closed, this, [=]() { ui.jobs->removeWidget(widget); ui.jobs->removeWidget(line); widget->deleteLater(); delete line; if (ui.jobs->count() == 2) { ui.noJobsAvailable->show(); } }); if (ui.jobs->count() == 2) { ui.noJobsAvailable->hide(); } ui.jobs->insertWidget(0, widget); ui.jobs->insertWidget(1, line); ui.tabs->setTabText(1, QString("Jobs (%1)").arg(++mJobCount)); auto settings = GetSettings(); QString opt = settings->value("Settings/mount").toString(); bool driveShared = settings->value("Settings/driveShared").toBool(); QStringList args; args << "mount"; #if defined(Q_OS_WIN32) args << "--rc"; args << "--rc-addr"; // calculate remote control interface port based on mount drive letter // this way every mount will have unique port assigned int port_offset = folder[0].toLatin1(); unsigned short int rclone_rc_port_base = 19000; unsigned short int rclone_rc_port = rclone_rc_port_base + port_offset; args << "localhost:" + QVariant(rclone_rc_port).toString(); #endif // for google drive "shared with me" without --read-only writes go created in // main google drive it is more logical to mount it as read only so there is // no confusion if (driveShared) { args << "--drive-shared-with-me"; args << "--read-only"; }; // default mount is now more generic. all options can be passed via // preferences mount field // args << "--vfs-cache-mode"; // args << "writes"; args.append(GetRcloneConf()); if (!opt.isEmpty()) { args.append(opt.split(' ')); } args << remote << folder; UseRclonePassword(mount); mount->start(GetRclone(), args, QIODevice::ReadOnly); } void MainWindow::addStream(const QString &remote, const QString &stream) { auto player = new QProcess(); auto rclone = new QProcess(); rclone->setStandardOutputProcess(player); QObject::connect( player, static_cast( &QProcess::finished), this, [=](int status, QProcess::ExitStatus) { player->deleteLater(); if (status != 0 && player->error() == QProcess::FailedToStart) { QMessageBox::critical( this, "Error", QString("Failed to start '%1' player process").arg(stream)); auto settings = GetSettings(); settings->remove("Settings/streamConfirmed"); } }); auto widget = new StreamWidget(rclone, player, remote, stream); auto line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); QObject::connect(widget, &StreamWidget::finished, this, [=]() { if (--mJobCount == 0) { ui.tabs->setTabText(1, "Jobs"); } else { ui.tabs->setTabText(1, QString("Jobs (%1)").arg(mJobCount)); } }); QObject::connect(widget, &StreamWidget::closed, this, [=]() { ui.jobs->removeWidget(widget); ui.jobs->removeWidget(line); widget->deleteLater(); delete line; if (ui.jobs->count() == 2) { ui.noJobsAvailable->show(); } }); if (ui.jobs->count() == 2) { ui.noJobsAvailable->hide(); } ui.jobs->insertWidget(0, widget); ui.jobs->insertWidget(1, line); ui.tabs->setTabText(1, QString("Jobs (%1)").arg(++mJobCount)); player->start(stream, QProcess::ReadOnly); UseRclonePassword(rclone); rclone->start(GetRclone(), QStringList() << "cat" << GetRcloneConf() << remote, QProcess::WriteOnly); } RcloneBrowser-1.8.0/src/main_window.h000066400000000000000000000021771362250633600175760ustar00rootroot00000000000000#pragma once #include "icon_cache.h" #include "job_options.h" #include "pch.h" #include "ui_main_window.h" class JobWidget; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); ~MainWindow(); private slots: void rcloneGetVersion(); void rcloneConfig(); void rcloneListRemotes(); void listTasks(); void addTransfer(const QString &message, const QString &source, const QString &dest, const QStringList &args); void addMount(const QString &remote, const QString &folder); void addStream(const QString &remote, const QString &stream); private: Ui::MainWindow ui; QSystemTrayIcon mSystemTray; JobWidget *mLastFinished = nullptr; bool mAlwaysShowInTray; bool mCloseToTray; bool mNotifyFinishedTransfers; QLabel *mStatusMessage; IconCache mIcons; bool mFirstTime = true; int mJobCount = 0; bool canClose(); void closeEvent(QCloseEvent *ev) override; bool getConfigPassword(QProcess *p); void addEmptyJobsMessage(); void runItem(JobOptionsListWidgetItem *item, bool dryrun = false); void editSelectedTask(); QIcon mUploadIcon; QIcon mDownloadIcon; }; RcloneBrowser-1.8.0/src/main_window.ui000066400000000000000000000330001362250633600177510ustar00rootroot00000000000000 MainWindow 0 0 658 411 Rclone Browser 0 0 0 0 0 0 true Remotes QAbstractItemView::NoEditTriggers QAbstractItemView::SelectRows 0 0 <html><head/><body><p>rclone config</p></body></html> &Config... Refresh Qt::Horizontal 40 20 false <html><head/><body><p>open remote</p></body></html> &Open Jobs true 0 0 610 299 0 0 0 0 No jobs are available Qt::Vertical 20 40 Tasks 4 4 0 0 true 0 0 646 327 0 0 200 20 16777215 32 4 4 4 4 4 false 0 0 0 28 -> Dry Run false 0 0 0 28 Run false 0 0 0 28 100 16777215 Qt::LeftToRight Edit false 0 0 0 28 Qt::LeftToRight Delete 0 0 658 22 &File &Help &About QAction::AboutRole About &Qt QAction::AboutQtRole &Preferences... QAction::PreferencesRole &Quit Ctrl+Q QAction::QuitRole tabs remotes config refresh open jobsArea RcloneBrowser-1.8.0/src/mount_widget.cpp000066400000000000000000000071401362250633600203160ustar00rootroot00000000000000#include "mount_widget.h" #include "utils.h" MountWidget::MountWidget(QProcess *process, const QString &remote, const QString &folder, QWidget *parent) : QWidget(parent), mProcess(process) { ui.setupUi(this); ui.remote->setText(remote); ui.folder->setText(folder); ui.info->setText(QString("%1 on %2").arg(remote).arg(folder)); ui.details->setVisible(false); ui.output->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui.output->setVisible(false); QObject::connect( ui.showDetails, &QToolButton::toggled, this, [=](bool checked) { ui.details->setVisible(checked); ui.showDetails->setArrowType(checked ? Qt::DownArrow : Qt::RightArrow); }); QObject::connect( ui.showOutput, &QToolButton::toggled, this, [=](bool checked) { ui.output->setVisible(checked); ui.showOutput->setArrowType(checked ? Qt::DownArrow : Qt::RightArrow); }); ui.cancel->setIcon( QApplication::style()->standardIcon(QStyle::SP_DialogCloseButton)); QObject::connect(ui.cancel, &QToolButton::clicked, this, [=]() { if (mRunning) { int button = QMessageBox::question( this, "Unmount", #if defined(Q_OS_WIN) QString("Do you want to unmount %1 drive?").arg(folder), #else QString("Do you want to unmount %1 folder?").arg(folder), #endif QMessageBox::Yes | QMessageBox::No); if (button == QMessageBox::Yes) { cancel(); } } else { emit closed(); } }); QObject::connect(mProcess, &QProcess::readyRead, this, [=]() { while (mProcess->canReadLine()) { ui.output->appendPlainText(mProcess->readLine().trimmed()); } }); QObject::connect(mProcess, static_cast( &QProcess::finished), this, [=](int status, QProcess::ExitStatus) { mProcess->deleteLater(); mRunning = false; if (status == 0) { ui.showDetails->setStyleSheet( "QToolButton { border: 0; color: black; }"); ui.showDetails->setText("Finished"); } else { ui.showDetails->setStyleSheet( "QToolButton { border: 0; color: red; }"); ui.showDetails->setText("Error"); } ui.cancel->setToolTip("Close"); emit finished(); }); ui.showDetails->setStyleSheet("QToolButton { border: 0; color: green; }"); ui.showDetails->setText("Mounted"); } MountWidget::~MountWidget() {} void MountWidget::cancel() { if (!mRunning) { return; } QString cmd; #if defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) QProcess::startDetached("umount", QStringList() << ui.folder->text()); #else #if defined(Q_OS_WIN32) QProcess *p = new QProcess(); QStringList args; args << "rc"; // requires rlone version at least 1.50 args << "core/quit"; args << "--rc-addr"; QString folder = ui.folder->text(); int port_offset = folder[0].toLatin1(); unsigned short int rclone_rc_port_base = 19000; unsigned short int rclone_rc_port = rclone_rc_port_base + port_offset; args << "localhost:" + QVariant(rclone_rc_port).toString(); UseRclonePassword(p); p->start(GetRclone(), args, QIODevice::ReadOnly); #else QProcess::startDetached("fusermount", QStringList() << "-u" << ui.folder->text()); #endif #endif mProcess->waitForFinished(); emit closed(); } RcloneBrowser-1.8.0/src/mount_widget.h000066400000000000000000000006411362250633600177620ustar00rootroot00000000000000#pragma once #include "pch.h" #include "ui_mount_widget.h" class MountWidget : public QWidget { Q_OBJECT public: MountWidget(QProcess *process, const QString &remote, const QString &folder, QWidget *parent = nullptr); ~MountWidget(); public slots: void cancel(); signals: void finished(); void closed(); private: Ui::MountWidget ui; bool mRunning = true; QProcess *mProcess; }; RcloneBrowser-1.8.0/src/mount_widget.ui000066400000000000000000000112651362250633600201540ustar00rootroot00000000000000 MountWidget 0 0 654 280 Form 0 0 0 0 0 true Qt::ToolButtonTextBesideIcon Qt::RightArrow Unmount QToolButton { border: 0 } 0 0 0 true true QToolButton { border: 0 } Show Output true Qt::ToolButtonTextBesideIcon Qt::RightArrow QPlainTextEdit::NoWrap true 0 0 Mount point: folder 0 0 Remote: remote showDetails cancel remote folder showOutput output RcloneBrowser-1.8.0/src/osx_helper.h000066400000000000000000000001741362250633600174260ustar00rootroot00000000000000#pragma once #include "pch.h" QIcon osxGetIcon(const QString &extension); void osxHideDockIcon(); void osxShowDockIcon(); RcloneBrowser-1.8.0/src/osx_helper.mm000066400000000000000000000015661362250633600176160ustar00rootroot00000000000000#include "osx_helper.h" #include #include QIcon osxGetIcon(const QString& extension) { QIcon icon; @autoreleasepool { NSImage* image = [[NSWorkspace sharedWorkspace] iconForFileType:extension.toNSString()]; NSRect rect = NSMakeRect(0, 0, image.size.width, image.size.height); CGImageRef imageRef = [image CGImageForProposedRect:&rect context:NULL hints:nil]; if (imageRef) { icon = QtMac::fromCGImageRef(imageRef); } } return icon; } void osxShowDockIcon() { ProcessSerialNumber psn = { 0, kCurrentProcess }; TransformProcessType(&psn, kProcessTransformToForegroundApplication); } void osxHideDockIcon() { ProcessSerialNumber psn = { 0, kCurrentProcess }; TransformProcessType(&psn, kProcessTransformToUIElementApplication); } RcloneBrowser-1.8.0/src/pch.cpp000066400000000000000000000000211362250633600163520ustar00rootroot00000000000000#include "pch.h" RcloneBrowser-1.8.0/src/pch.h000066400000000000000000000005061362250633600160270ustar00rootroot00000000000000#pragma once #ifdef _MSC_VER #pragma warning(push, 0) #endif #include #include #include #include #include #include #if defined(Q_OS_WIN32) #include #endif #ifdef Q_OS_MACOS #include #endif #ifdef _MSC_VER #pragma warning pop #endif RcloneBrowser-1.8.0/src/preferences_dialog.cpp000066400000000000000000000205531362250633600214340ustar00rootroot00000000000000#include "preferences_dialog.h" #include "utils.h" PreferencesDialog::PreferencesDialog(QWidget *parent) : QDialog(parent) { ui.setupUi(this); QObject::connect(ui.rcloneBrowse, &QPushButton::clicked, this, [=]() { QString rclone = QFileDialog::getOpenFileName( this, "Select rclone executable", ui.rclone->text()); if (rclone.isEmpty()) { return; } if (!QFileInfo(rclone).isExecutable()) { QMessageBox::critical(this, "Error", QString("File %1 is not executable").arg(rclone)); return; } if (QFileInfo(rclone) == QFileInfo(qApp->applicationFilePath())) { QMessageBox::critical(this, "Error", "You selected RcloneBrowser executable!\nPlease " "select rclone executable instead."); return; } ui.rclone->setText(rclone); }); QObject::connect(ui.rcloneConfBrowse, &QPushButton::clicked, this, [=]() { QString rcloneConf = QFileDialog::getOpenFileName( this, "Select .rclone.conf location", ui.rcloneConf->text()); if (rcloneConf.isEmpty()) { return; } ui.rcloneConf->setText(rcloneConf); }); QObject::connect( ui.defaultDownloadDirBrowse, &QPushButton::clicked, this, [=]() { QString defaultDownloadDir = QFileDialog::getExistingDirectory( this, "Select default download directory", ui.defaultDownloadDir->text()); if (defaultDownloadDir.isEmpty()) { return; } ui.defaultDownloadDir->setText(defaultDownloadDir); }); QObject::connect( ui.defaultUploadDirBrowse, &QPushButton::clicked, this, [=]() { QString defaultUploadDir = QFileDialog::getExistingDirectory( this, "Select default upload directory", ui.defaultUploadDir->text()); if (defaultUploadDir.isEmpty()) { return; } ui.defaultUploadDir->setText(defaultUploadDir); }); auto settings = GetSettings(); ui.rclone->setText( QDir::toNativeSeparators(settings->value("Settings/rclone").toString())); ui.rcloneConf->setText(QDir::toNativeSeparators( settings->value("Settings/rcloneConf").toString())); ui.stream->setText(settings->value("Settings/stream").toString()); #if defined(Q_OS_OPENBSD) || defined(Q_OS_NETBSD) ui.mount->setText( settings ->value("Settings/mount", "* mount is not supported by rclone on this system *") .toString()); ui.mount->setDisabled(true); #else ui.mount->setText( settings->value("Settings/mount", "--vfs-cache-mode writes").toString()); #endif ui.defaultDownloadDir->setText(QDir::toNativeSeparators( settings->value("Settings/defaultDownloadDir").toString())); ui.defaultUploadDir->setText(QDir::toNativeSeparators( settings->value("Settings/defaultUploadDir").toString())); ui.defaultDownloadOptions->setText( settings->value("Settings/defaultDownloadOptions").toString()); ui.defaultUploadOptions->setText( settings->value("Settings/defaultUploadOptions").toString()); ui.defaultRcloneOptions->setText( settings->value("Settings/defaultRcloneOptions").toString()); ui.checkRcloneBrowserUpdates->setChecked( settings->value("Settings/checkRcloneBrowserUpdates", true).toBool()); ui.checkRcloneUpdates->setChecked( settings->value("Settings/checkRcloneUpdates", true).toBool()); if (QSystemTrayIcon::isSystemTrayAvailable()) { ui.alwaysShowInTray->setChecked( settings->value("Settings/alwaysShowInTray", false).toBool()); ui.closeToTray->setChecked( settings->value("Settings/closeToTray", false).toBool()); ui.notifyFinishedTransfers->setChecked( settings->value("Settings/notifyFinishedTransfers", true).toBool()); } else { ui.alwaysShowInTray->setChecked(false); ui.alwaysShowInTray->setDisabled(true); ui.closeToTray->setChecked(false); ui.closeToTray->setDisabled(true); ui.notifyFinishedTransfers->setChecked(false); ui.notifyFinishedTransfers->setDisabled(true); } ui.showFolderIcons->setChecked( settings->value("Settings/showFolderIcons", true).toBool()); ui.showFileIcons->setChecked( settings->value("Settings/showFileIcons", true).toBool()); ui.rowColors->setChecked( settings->value("Settings/rowColors", true).toBool()); ui.showHidden->setChecked( settings->value("Settings/showHidden", true).toBool()); ui.darkMode->setChecked(settings->value("Settings/darkMode", true).toBool()); // dark mode option for all systems but latest macOS // on macOS Mojave or newer dark mode is managed by OS #if defined(Q_OS_MACOS) QString sysInfo = QSysInfo::productVersion(); if (sysInfo == "10.9" || sysInfo == "10.10" || sysInfo == "10.11" || sysInfo == "10.12" || sysInfo == "10.13") { } else { ui.darkMode->hide(); ui.darkMode_info->hide(); } #endif if ((settings->value("Settings/iconSize").toString()) == "small") { ui.cb_small->setChecked(true); } else { if (settings->value("Settings/iconSize").toString() == "large") { ui.cb_large->setChecked(true); } else { ui.cb_medium->setChecked(true); } } ui.info_2->setText( "See rclone FAQ for details."); ui.info_2->setTextFormat(Qt::RichText); ui.info_2->setTextInteractionFlags(Qt::TextBrowserInteraction); ui.info_2->setOpenExternalLinks(true); if (settings->value("Settings/useProxy").toBool()) { ui.useProxy->setChecked(true); } else { ui.useSystemSettings->setChecked(true); } ui.http_proxy->setText(settings->value("Settings/http_proxy").toString()); ui.https_proxy->setText(settings->value("Settings/https_proxy").toString()); ui.no_proxy->setText(settings->value("Settings/no_proxy").toString()); } PreferencesDialog::~PreferencesDialog() {} QString PreferencesDialog::getRclone() const { return QDir::fromNativeSeparators(ui.rclone->text()); } QString PreferencesDialog::getRcloneConf() const { return QDir::fromNativeSeparators(ui.rcloneConf->text()); } QString PreferencesDialog::getStream() const { return ui.stream->text(); } QString PreferencesDialog::getMount() const { return ui.mount->text(); } QString PreferencesDialog::getDefaultDownloadDir() const { return QDir::fromNativeSeparators(ui.defaultDownloadDir->text()); } QString PreferencesDialog::getDefaultUploadDir() const { return QDir::fromNativeSeparators(ui.defaultUploadDir->text()); } QString PreferencesDialog::getDefaultDownloadOptions() const { return ui.defaultDownloadOptions->text(); } QString PreferencesDialog::getDefaultUploadOptions() const { return ui.defaultUploadOptions->text(); } QString PreferencesDialog::getDefaultRcloneOptions() const { return ui.defaultRcloneOptions->text(); } bool PreferencesDialog::getCheckRcloneBrowserUpdates() const { return ui.checkRcloneBrowserUpdates->isChecked(); } bool PreferencesDialog::getCheckRcloneUpdates() const { return ui.checkRcloneUpdates->isChecked(); } bool PreferencesDialog::getAlwaysShowInTray() const { return ui.alwaysShowInTray->isChecked(); } bool PreferencesDialog::getCloseToTray() const { return ui.closeToTray->isChecked(); } bool PreferencesDialog::getNotifyFinishedTransfers() const { return ui.notifyFinishedTransfers->isChecked(); } bool PreferencesDialog::getShowFolderIcons() const { return ui.showFolderIcons->isChecked(); } bool PreferencesDialog::getShowFileIcons() const { return ui.showFileIcons->isChecked(); } bool PreferencesDialog::getRowColors() const { return ui.rowColors->isChecked(); } bool PreferencesDialog::getShowHidden() const { return ui.showHidden->isChecked(); } bool PreferencesDialog::getDarkMode() const { return ui.darkMode->isChecked(); } QString PreferencesDialog::getIconSize() const { if (ui.cb_small->isChecked()) { return "small"; } else { if (ui.cb_large->isChecked()) { return "large"; } else { return "medium"; } } } QString PreferencesDialog::getHttpProxy() const { return ui.http_proxy->text(); } QString PreferencesDialog::getHttpsProxy() const { return ui.https_proxy->text(); } QString PreferencesDialog::getNoProxy() const { return ui.no_proxy->text(); } bool PreferencesDialog::getUseProxy() const { if (ui.useSystemSettings->isChecked()) { return false; } else { return true; } } RcloneBrowser-1.8.0/src/preferences_dialog.h000066400000000000000000000020701362250633600210730ustar00rootroot00000000000000#pragma once #include "pch.h" #include "ui_preferences_dialog.h" class PreferencesDialog : public QDialog { Q_OBJECT public: PreferencesDialog(QWidget *parent = nullptr); ~PreferencesDialog(); QString getRclone() const; QString getRcloneConf() const; QString getStream() const; QString getMount() const; QString getDefaultDownloadDir() const; QString getDefaultUploadDir() const; QString getDefaultDownloadOptions() const; QString getDefaultUploadOptions() const; QString getDefaultRcloneOptions() const; bool getCheckRcloneBrowserUpdates() const; bool getCheckRcloneUpdates() const; bool getAlwaysShowInTray() const; bool getCloseToTray() const; bool getNotifyFinishedTransfers() const; bool getShowFolderIcons() const; bool getShowFileIcons() const; bool getRowColors() const; bool getShowHidden() const; bool getDarkMode() const; QString getIconSize() const; bool getUseProxy() const; QString getHttpProxy() const; QString getHttpsProxy() const; QString getNoProxy() const; private: Ui::PreferencesDialog ui; }; RcloneBrowser-1.8.0/src/preferences_dialog.ui000077500000000000000000000646441362250633600213030ustar00rootroot00000000000000 PreferencesDialog 0 0 751 537 0 0 Preferences 0 0 0 General Settings false false 12 -1 ... rclone.conf location: rcloneConf Default rclone options: defaultRcloneOptions ... <html><head/><body><p>rclone executable file location</p></body></html> Mount options: mount <html><head/><body><p>extra rclone options used for downloads</p></body></html> <html><head/><body><p>default download folder used for transfers</p></body></html> rclone location: rclone ... <html><head/><body><p>rclone configuration file locaction. if empty default location defined by rclone is used</p></body></html> Default upload options: defaultUploadOptions Stream command: stream <html><head/><body><p>options used by rclone for remotes mounting</p></body></html> <html><head/><body><p>extra rclone options used for uploads</p></body></html> <html><head/><body><p>default upload folder used for transfers</p></body></html> Default download folder: defaultDownloadDir Default download options: defaultDownloadOptions ... <html><head/><body><p>extra rclone options used for all operations</p></body></html> Default upload folder: defaultUploadDir <html><head/><body><p>location of audio/video player</p></body></html> label_u label_d label_11 label_12 label_2 mountLabel label_10 label label_1 defaultUploadOptions defaultRcloneOptions defaultDownloadOptions defaultUploadDir defaultDownloadDirBrowse defaultUploadDirBrowse mount stream rcloneConf rclone rcloneBrowse rcloneConfBrowse defaultDownloadDir Qt::Vertical QSizePolicy::Fixed 0 10 0 0 Updates notifications false 12 12 <html><head/><body><p>check daily for Rclone Browser availability</p></body></html> Check for Rclone Browser updates <html><head/><body><p>check daily for rclone availability</p></body></html> Check for rclone updates Qt::Vertical 20 0 Interface System Tray Always show in system tray Close to system tray Notify about finished transfers Qt::Vertical QSizePolicy::Fixed 0 10 User Interface 0 0 Show file icons Alternating row colours Show hidden files and folders Changing above options will require reopening remote tabs. Changing this option will require restarting the app. 0 0 Show folder icons Enable dark mode Qt::Vertical QSizePolicy::Fixed 0 10 true 0 0 Remotes icons size Small true false Medium true true Large true Qt::Horizontal 40 20 Qt::Vertical 20 0 Proxy Connection settings <html><head/><body><p>rclone will use proxy defined below to access Internet</p></body></html> Use manual proxy configuration true 0 0 <html><head/><body><p>rclone will use OS defined internet access</p></body></html> Use system settings true true Qt::Vertical QSizePolicy::Fixed 0 10 Manual proxy configuration e.g. localhost,127.0.0.0/8,cs.aliyuncs.com e.g. http://127.0.0.1:1087 http_proxy: http_proxy <html><head/><body><p><br/></p></body></html> no_proxy: no_proxy e.g. https://127.0.0.1:1087 https_proxy: https_proxy For proxy settings changes to take effect Rclone Browser has to be restarted. Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok rclone rcloneBrowse rcloneConf rcloneConfBrowse stream mount defaultDownloadDir defaultDownloadDirBrowse defaultUploadDir defaultUploadDirBrowse defaultDownloadOptions defaultUploadOptions defaultRcloneOptions checkRcloneBrowserUpdates checkRcloneUpdates tabWidget alwaysShowInTray closeToTray notifyFinishedTransfers showFolderIcons showFileIcons rowColors showHidden darkMode cb_small cb_medium cb_large useSystemSettings useProxy http_proxy https_proxy no_proxy buttonBox accepted() PreferencesDialog accept() 268 512 157 274 buttonBox rejected() PreferencesDialog reject() 336 512 286 274 RcloneBrowser-1.8.0/src/progress_dialog.cpp000066400000000000000000000043161362250633600207760ustar00rootroot00000000000000#include "progress_dialog.h" ProgressDialog::ProgressDialog(const QString &title, const QString &operation, const QString &message, QProcess *process, QWidget *parent, bool close, bool trim) : QDialog(parent) { ui.setupUi(this); resize(width(), 0); setWindowTitle(title); ui.labelOperation->setText(operation); ui.labelInfo->setText(message); ui.output->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui.output->setVisible(false); QObject::connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QObject::connect(ui.buttonShowOutput, &QPushButton::toggled, this, [=](bool checked) { ui.output->setVisible(checked); ui.buttonShowOutput->setArrowType( checked ? Qt::DownArrow : Qt::RightArrow); if (!checked) { adjustSize(); } }); QObject::connect(process, static_cast( &QProcess::finished), this, [=](int code, QProcess::ExitStatus status) { if (status == QProcess::NormalExit && code == 0) { if (close) { emit accept(); } } else { ui.buttonShowOutput->setChecked(true); ui.buttonBox->setEnabled(true); } }); QObject::connect(process, &QProcess::readyRead, this, [=]() { QString output = process->readAll(); if (trim) { output = output.trimmed(); } ui.output->appendPlainText(output); emit outputAvailable(output); }); process->setProcessChannelMode(QProcess::MergedChannels); process->start(QIODevice::ReadOnly); } ProgressDialog::~ProgressDialog() {} void ProgressDialog::expand() { ui.buttonShowOutput->setChecked(true); } void ProgressDialog::allowToClose() { ui.buttonBox->setEnabled(true); } // // QString ProgressDialog::getOutput() const //{ // return ui.output->toPlainText(); //} RcloneBrowser-1.8.0/src/progress_dialog.h000066400000000000000000000010001362250633600204260ustar00rootroot00000000000000#pragma once #include "pch.h" #include "ui_progress_dialog.h" class ProgressDialog : public QDialog { Q_OBJECT public: ProgressDialog(const QString &title, const QString &operation, const QString &message, QProcess *process, QWidget *parent = nullptr, bool close = true, bool trim = false); ~ProgressDialog(); void expand(); void allowToClose(); signals: void outputAvailable(const QString &output) const; private: Ui::ProgressDialog ui; }; RcloneBrowser-1.8.0/src/progress_dialog.ui000066400000000000000000000046631362250633600206360ustar00rootroot00000000000000 ProgressDialog 0 0 618 291 0 0 600 200 QPlainTextEdit::NoWrap false QDialogButtonBox::Close 0 0 QToolButton { border: none; } Show Output true Qt::ToolButtonTextBesideIcon Qt::RightArrow 0 0 buttonShowOutput output RcloneBrowser-1.8.0/src/remote_widget.cpp000066400000000000000000000620561362250633600204560ustar00rootroot00000000000000#include "remote_widget.h" #include "export_dialog.h" #include "icon_cache.h" #include "item_model.h" #include "list_of_job_options.h" #include "progress_dialog.h" #include "transfer_dialog.h" #include "utils.h" RemoteWidget::RemoteWidget(IconCache *iconCache, const QString &remote, bool isLocal, bool isGoogle, QWidget *parent) : QWidget(parent) { ui.setupUi(this); QString root = isLocal ? "/" : QString(); #ifndef Q_OS_WIN isLocal = false; #endif auto settings = GetSettings(); QString rcloneVersion = settings->value("Settings/rcloneVersion").toString(); settings->setValue("Settings/driveShared", Qt::Unchecked); ui.tree->setAlternatingRowColors( settings->value("Settings/rowColors", false).toBool()); ui.checkBoxShared->setChecked(false); ui.checkBoxShared->setDisabled(!isGoogle); // hide checkBoxShared for non Google remotes if (!isGoogle) { ui.checkBoxShared->hide(); } QStyle *style = QApplication::style(); ui.refresh->setIcon(style->standardIcon(QStyle::SP_BrowserReload)); ui.mkdir->setIcon(style->standardIcon(QStyle::SP_FileDialogNewFolder)); ui.rename->setIcon(style->standardIcon(QStyle::SP_FileIcon)); ui.move->setIcon(style->standardIcon(QStyle::SP_DirOpenIcon)); ui.purge->setIcon(style->standardIcon(QStyle::SP_TrashIcon)); ui.mount->setIcon(style->standardIcon(QStyle::SP_DriveNetIcon)); ui.stream->setIcon(style->standardIcon(QStyle::SP_MediaPlay)); ui.upload->setIcon(style->standardIcon(QStyle::SP_ArrowUp)); ui.download->setIcon(style->standardIcon(QStyle::SP_ArrowDown)); ui.download->setIcon(style->standardIcon(QStyle::SP_ArrowDown)); ui.getSize->setIcon(style->standardIcon(QStyle::SP_FileDialogInfoView)); ui.getTree->setIcon(style->standardIcon(QStyle::SP_FileDialogListView)); ui.export_->setIcon(style->standardIcon(QStyle::SP_FileDialogDetailedView)); ui.link->setIcon(style->standardIcon(QStyle::SP_FileLinkIcon)); ui.buttonRefresh->setDefaultAction(ui.refresh); ui.buttonMkdir->setDefaultAction(ui.mkdir); ui.buttonRename->setDefaultAction(ui.rename); ui.buttonMove->setDefaultAction(ui.move); ui.buttonPurge->setDefaultAction(ui.purge); ui.buttonMount->setDefaultAction(ui.mount); ui.buttonStream->setDefaultAction(ui.stream); ui.buttonUpload->setDefaultAction(ui.upload); ui.buttonDownload->setDefaultAction(ui.download); ui.buttonTree->setDefaultAction(ui.getTree); ui.buttonLink->setDefaultAction(ui.link); ui.buttonSize->setDefaultAction(ui.getSize); ui.buttonExport->setDefaultAction(ui.export_); ui.tree->sortByColumn(0, Qt::AscendingOrder); ui.tree->header()->setSectionsMovable(false); ItemModel *model = new ItemModel(iconCache, remote, this); ui.tree->setModel(model); QTimer::singleShot(0, ui.tree, SLOT(setFocus())); QObject::connect(model, &QAbstractItemModel::layoutChanged, this, [=]() { ui.tree->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui.tree->resizeColumnToContents(1); ui.tree->resizeColumnToContents(2); }); QObject::connect( ui.tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, [=](const QItemSelection &selection) { for (auto child : findChildren()) { child->setDisabled(selection.isEmpty()); } if (selection.isEmpty()) { ui.path->clear(); return; } QModelIndex index = selection.indexes().front(); bool topLevel = model->isTopLevel(index); bool isFolder = model->isFolder(index); QDir path; if (model->isLoading(index)) { ui.refresh->setDisabled(true); ui.move->setDisabled(true); ui.rename->setDisabled(true); ui.purge->setDisabled(true); ui.mount->setDisabled(true); ui.stream->setDisabled(true); ui.upload->setDisabled(true); ui.download->setDisabled(true); ui.checkBoxShared->setDisabled(true); path = model->path(model->parent(index)); } else { ui.refresh->setDisabled(false); bool driveShared = ui.checkBoxShared->checkState(); ui.mkdir->setDisabled(driveShared); ui.rename->setDisabled(topLevel || driveShared); ui.move->setDisabled(topLevel || driveShared); ui.purge->setDisabled(topLevel || driveShared); ui.upload->setDisabled(driveShared); #if defined(Q_OS_WIN32) // check if required version unsigned int result = compareVersion(rcloneVersion.toStdString(), "1.50"); if (result == 2) { ui.mount->setDisabled(true); } else { ui.mount->setDisabled(!isFolder); }; #else // mount is not supported by rclone on these systems #if defined(Q_OS_OPENBSD) || defined(Q_OS_NETBSD) ui.mount->setDisabled(true); #else ui.mount->setDisabled(!isFolder); #endif #endif ui.stream->setDisabled(isFolder); ui.checkBoxShared->setDisabled(!isGoogle); path = model->path(index); } ui.getSize->setDisabled(!isFolder); ui.getTree->setDisabled(!isFolder); ui.export_->setDisabled(!isFolder); ui.path->setText(isLocal ? QDir::toNativeSeparators(path.path()) : path.path()); }); QObject::connect(ui.refresh, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); model->refresh(index); }); QObject::connect(ui.mkdir, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); if (!model->isFolder(index)) { index = index.parent(); } QDir path = model->path(index); QString pathMsg = isLocal ? QDir::toNativeSeparators(path.path()) : path.path(); QString name = QInputDialog::getText( this, "New Folder", QString("Create folder in %1").arg(pathMsg)); if (!name.isEmpty()) { QString folder = path.filePath(name); QString folderMsg = isLocal ? QDir::toNativeSeparators(folder) : folder; QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments(QStringList() << "mkdir" << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << remote + ":" + folder); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("New Folder", "Creating...", folderMsg, &process, this); if (progress.exec() == QDialog::Accepted) { model->refresh(index); } } }); QObject::connect(ui.rename, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; QString name = model->data(index, Qt::DisplayRole).toString(); name = QInputDialog::getText(this, "Rename", QString("New name for %1").arg(pathMsg), QLineEdit::Normal, name); if (!name.isEmpty()) { QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments( QStringList() << "moveto" << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << remote + ":" + path << remote + ":" + model->path(index.parent()).filePath(name)); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("Rename", "Renaming...", pathMsg, &process, this); if (progress.exec() == QDialog::Accepted) { model->rename(index, name); } } }); QObject::connect(ui.move, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; QString name = model->path(index.parent()).path() + "/"; name = QInputDialog::getText(this, "Move", QString("New location for %1").arg(pathMsg), QLineEdit::Normal, name); if (!name.isEmpty()) { QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments( QStringList() << "move" << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << remote + ":" + path << remote + ":" + name); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("Move", "Moving...", pathMsg, &process, this); if (progress.exec() == QDialog::Accepted) { model->refresh(index); } } }); QObject::connect(ui.purge, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; int button = QMessageBox::question( this, "Delete", QString("Are you sure you want to delete %1 ?").arg(pathMsg), QMessageBox::Yes | QMessageBox::No); if (button == QMessageBox::Yes) { QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments(QStringList() << (model->isFolder(index) ? "purge" : "delete") << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << remote + ":" + path); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("Delete", "Deleting...", pathMsg, &process, this); if (progress.exec() == QDialog::Accepted) { QModelIndex parent = index.parent(); QModelIndex next = parent.model()->index(index.row() + 1, 0); ui.tree->selectionModel()->select(next.isValid() ? next : parent, QItemSelectionModel::SelectCurrent); model->removeRow(index.row(), parent); } } }); QObject::connect(ui.mount, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; #if defined(Q_OS_WIN32) QString folder = QInputDialog::getText(this, "Mount", QString("(Make sure you have WinFsp-FUSE " "installed)\n\nDrive to mount %1 to") .arg(remote), QLineEdit::Normal, "Z:"); #else QString folder = QFileDialog::getExistingDirectory(this, QString("Mount %1").arg(remote)); #endif if (!folder.isEmpty()) { emit addMount(remote + ":" + path, folder); } }); QObject::connect(ui.stream, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); bool streamConfirmed = settings->value("Settings/streamConfirmed", false).toBool(); QString stream = settings->value("Settings/stream", "mpv -").toString(); if (!streamConfirmed) { QString result = QInputDialog::getText( this, "Stream", "Enter stream command (file will be passed in STDIN):", QLineEdit::Normal, stream); if (result.isEmpty()) { return; } stream = result; settings->setValue("Settings/stream", stream); settings->setValue("Settings/streamConfirmed", true); } emit addStream(remote + ":" + path, stream); }); QObject::connect(ui.checkBoxShared, &QCheckBox::toggled, ui.shared, &QAction::toggled); QObject::connect(ui.shared, &QAction::toggled, this, [=](const bool checked) { auto settings = GetSettings(); settings->setValue("Settings/driveShared", checked); ui.checkBoxShared->setChecked(checked); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QModelIndex top = index; while (!model->isTopLevel(top)) { top = top.parent(); } ui.tree->selectionModel()->clear(); ui.tree->selectionModel()->select(top, QItemSelectionModel::Select | QItemSelectionModel::Rows); model->refresh(top); }); QObject::connect(ui.link, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments( QStringList() << "link" << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << remote + ":" + path); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("Fetch Public Link", "Fetching link for...", pathMsg, &process, this, false, true); progress.expand(); progress.allowToClose(); progress.exec(); }); QObject::connect(ui.upload, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); if (!model->isFolder(index)) { index = index.parent(); } QDir path = model->path(index); TransferDialog t(false, false, remote, path, true, this); if (t.exec() == QDialog::Accepted) { QString src = t.getSource(); QString dst = t.getDest(); QStringList args = t.getOptions(); emit addTransfer(QString("%1 from %2").arg(t.getMode()).arg(src), src, dst, args); } }); QObject::connect(ui.download, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QDir path = model->path(index); TransferDialog t(true, false, remote, path, model->isFolder(index), this); if (t.exec() == QDialog::Accepted) { QString src = t.getSource(); QString dst = t.getDest(); QStringList args = t.getOptions(); emit addTransfer(QString("%1 %2").arg(t.getMode()).arg(src), src, dst, args); } }); QObject::connect(ui.getTree, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments( QStringList() << "tree" << "-d" << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << remote + ":" + path); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("Show directories tree", "Processing...", pathMsg, &process, this, false); progress.expand(); progress.allowToClose(); progress.resize(1000, 600); progress.exec(); }); QObject::connect(ui.getSize, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments( QStringList() << "size" << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << remote + ":" + path); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("Get Size", "Calculating...", pathMsg, &process, this, false); progress.expand(); progress.allowToClose(); progress.exec(); }); QObject::connect(ui.export_, &QAction::triggered, this, [=]() { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QDir path = model->path(index); ExportDialog e(remote, path, this); if (e.exec() == QDialog::Accepted) { QString dst = e.getDestination(); bool txt = e.onlyFilenames(); QFile *file = new QFile(dst); if (!file->open(QFile::WriteOnly)) { QMessageBox::warning( this, "Error", QString("Cannot open file '%1' for writing!").arg(dst)); delete file; return; } QRegExp re(R"(^(\d+) (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\.\d+ (.+)$)"); QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); process.setArguments(QStringList() << GetRcloneConf() << GetDriveSharedWithMe() << GetDefaultRcloneOptionsList() << e.getOptions()); process.setProcessChannelMode(QProcess::MergedChannels); ProgressDialog progress("Export", "Exporting...", dst, &process, this); file->setParent(&progress); QObject::connect(&progress, &ProgressDialog::outputAvailable, this, [=](const QString &output) { QTextStream out(file); for (const auto &line : output.split('\n')) { if (re.exactMatch(line.trimmed())) { QStringList cap = re.capturedTexts(); if (txt) { out << cap[3] << '\n'; } else { QString name = cap[3]; if (name.contains(' ') || name.contains(',') || name.contains('"')) { name = '"' + name.replace("\"", "\"\"") + '"'; } out << name << ',' << '"' << cap[2] << '"' << ',' << cap[1].toULongLong() << '\n'; } } } }); progress.exec(); } }); QObject::connect( model, &ItemModel::drop, this, [=](const QDir &path, const QModelIndex &parent) { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); qApp->setActiveWindow(this); QDir destPath = model->path(parent); QString dest = QFileInfo(path.path()).isDir() ? destPath.filePath(path.dirName()) : destPath.path(); TransferDialog t(false, true, remote, dest, true, this); t.setSource(path.path()); if (t.exec() == QDialog::Accepted) { QString src = t.getSource(); QString dst = t.getDest(); QStringList args = t.getOptions(); emit addTransfer(QString("%1 from %2").arg(t.getMode()).arg(src), src, dst, args); } }); QObject::connect( ui.tree, &QWidget::customContextMenuRequested, this, [=](const QPoint &pos) { auto settings = GetSettings(); bool driveShared = ui.checkBoxShared->checkState(); (driveShared ? settings->setValue("Settings/driveShared", Qt::Checked) : settings->setValue("Settings/driveShared", Qt::Unchecked)); QMenu menu; menu.addAction(ui.refresh); menu.addAction(ui.getSize); menu.addAction(ui.getTree); menu.addAction(ui.export_); menu.addSeparator(); menu.addAction(ui.mkdir); menu.addAction(ui.rename); menu.addAction(ui.move); menu.addAction(ui.purge); menu.addSeparator(); menu.addAction(ui.mount); menu.addAction(ui.stream); menu.addAction(ui.upload); menu.addAction(ui.download); menu.addAction(ui.link); menu.exec(ui.tree->viewport()->mapToGlobal(pos)); }); if (isLocal) { QHash drives; // QDir::drives is fast for (const auto &drive : QDir::drives()) { QString path = drive.path(); QModelIndex index = model->addRoot(QDir::toNativeSeparators(path), path); drives.insert(path, index); } #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) && !(defined Q_OS_WIN) QThread *thread = new QThread(this); thread->start(); QObject *worker = new QObject(); worker->moveToThread(thread); QTimer::singleShot(0, worker, [=]() { QStorageInfo info; info.refresh(); // QStorageInfo::mountedVolumes is slow :( for (const auto &volume : info.mountedVolumes()) { QString name = volume.name(); if (!name.isEmpty()) { QString path = volume.rootPath(); QString item = QString("%1 (%2)").arg(QDir::toNativeSeparators(path)).arg(name); QTimer::singleShot(0, this, [=]() { model->rename(drives[path], item); }); } } thread->quit(); thread->deleteLater(); worker->deleteLater(); }); #endif ui.tree->selectionModel()->selectionChanged(QItemSelection(), QItemSelection()); } else { QModelIndex index = model->addRoot("/", root); ui.tree->selectionModel()->select( index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); ui.tree->expand(index); } QShortcut *close = new QShortcut(QKeySequence::Close, this); QObject::connect(close, &QShortcut::activated, this, [=]() { auto tabs = qobject_cast(parent); tabs->removeTab(tabs->indexOf(this)); }); } RemoteWidget::~RemoteWidget() {} RcloneBrowser-1.8.0/src/remote_widget.h000066400000000000000000000011201362250633600201040ustar00rootroot00000000000000#pragma once #include "pch.h" #include "ui_remote_widget.h" class IconCache; class RemoteWidget : public QWidget { Q_OBJECT public: RemoteWidget(IconCache *icons, const QString &remote, bool isLocal, bool isGoogle, QWidget *parent = nullptr); ~RemoteWidget(); signals: void addTransfer(const QString &message, const QString &source, const QString &remote, const QStringList &args); void addMount(const QString &remote, const QString &folder); void addStream(const QString &remote, const QString &stream); private: Ui::RemoteWidget ui; }; RcloneBrowser-1.8.0/src/remote_widget.ui000077500000000000000000000266321362250633600203140ustar00rootroot00000000000000 RemoteWidget 0 0 912 552 Form Qt::Horizontal false 0 0 0 0 0 0 0 0 &Refresh Qt::ToolButtonTextBesideIcon &New Folder Qt::ToolButtonTextBesideIcon R&ename Qt::ToolButtonTextBesideIcon M&ove Qt::ToolButtonTextBesideIcon De&lete Qt::ToolButtonTextBesideIcon &Mount Qt::ToolButtonTextBesideIcon &Stream Qt::ToolButtonTextBesideIcon &Upload... Qt::ToolButtonTextBesideIcon &Download... Qt::ToolButtonTextBesideIcon &Get Size... Qt::ToolButtonTextBesideIcon &Tree Qt::ToolButtonTextBesideIcon &Link Qt::ToolButtonTextBesideIcon E&xport... Qt::ToolButtonTextBesideIcon Qt::Horizontal 40 20 Shared with Me (Google Drive) Shared Qt::Horizontal QSizePolicy::Minimum 10 20 true Qt::CustomContextMenu true QAbstractItemView::NoEditTriggers true Qt::CopyAction QAbstractItemView::SingleSelection QAbstractItemView::SelectRows true true false &Refresh F5 &Mount rclone mount &Stream &New Folder rclone mkdir F7 R&ename rclone moveto F2 De&lete rclone purge|delete Del &Upload &Download &Get Size rclone size DirTree rclone tree E&xport Export files list M&ove rclone move Shared Public Link rclone link buttonRefresh buttonMkdir buttonRename buttonPurge buttonMount buttonStream buttonUpload buttonDownload buttonSize buttonTree path tree RcloneBrowser-1.8.0/src/resources.qrc000066400000000000000000000027351362250633600176330ustar00rootroot00000000000000 icon.png images/amazon_cloud_drive.png images/b2.png images/crypt.png images/drive.png images/dropbox.png images/google_cloud_storage.png images/hubic.png images/local.png images/mega.png images/onedrive.png images/s3.png images/ftp.png images/sftp.png images/swift.png images/unknown.png images/yandex.png images/azureblob.png images/google_photos.png images/amazon_cloud_drive_inv.png images/b2_inv.png images/crypt_inv.png images/drive_inv.png images/dropbox_inv.png images/google_cloud_storage_inv.png images/hubic_inv.png images/local_inv.png images/mega_inv.png images/onedrive_inv.png images/s3_inv.png images/ftp_inv.png images/sftp_inv.png images/swift_inv.png images/unknown_inv.png images/yandex_inv.png images/azureblob_inv.png images/google_photos_inv.png RcloneBrowser-1.8.0/src/resources.rc000066400000000000000000000000361362250633600174420ustar00rootroot000000000000001 ICON DISCARDABLE "icon.ico" RcloneBrowser-1.8.0/src/stream_widget.cpp000066400000000000000000000043331362250633600204500ustar00rootroot00000000000000#include "stream_widget.h" StreamWidget::StreamWidget(QProcess *rclone, QProcess *player, const QString &remote, const QString &stream, QWidget *parent) : QWidget(parent), mRclone(rclone), mPlayer(player) { ui.setupUi(this); ui.remote->setText(remote); ui.stream->setText(stream); ui.info->setText(remote); ui.details->setVisible(false); ui.output->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui.output->setVisible(false); QObject::connect( ui.showDetails, &QToolButton::toggled, this, [=](bool checked) { ui.details->setVisible(checked); ui.showDetails->setArrowType(checked ? Qt::DownArrow : Qt::RightArrow); }); QObject::connect( ui.showOutput, &QToolButton::toggled, this, [=](bool checked) { ui.output->setVisible(checked); ui.showOutput->setArrowType(checked ? Qt::DownArrow : Qt::RightArrow); }); ui.cancel->setIcon( QApplication::style()->standardIcon(QStyle::SP_DialogCloseButton)); QObject::connect(ui.cancel, &QToolButton::clicked, this, [=]() { if (mRunning) { int button = QMessageBox::question( this, "Stop", QString("Do you want to stop %1 stream?").arg(remote), QMessageBox::Yes | QMessageBox::No); if (button == QMessageBox::Yes) { cancel(); } } else { emit closed(); } }); QObject::connect(mRclone, &QProcess::readyRead, this, [=]() { while (mRclone->canReadLine()) { ui.output->appendPlainText(mRclone->readLine().trimmed()); } }); QObject::connect(mRclone, static_cast( &QProcess::finished), this, [=]() { mRclone->deleteLater(); mRunning = false; emit finished(); emit closed(); }); ui.showDetails->setStyleSheet("QToolButton { border: 0; color: green; }"); ui.showDetails->setText("Streaming"); } StreamWidget::~StreamWidget() {} void StreamWidget::cancel() { if (!mRunning) { return; } mPlayer->terminate(); mRclone->kill(); mRclone->waitForFinished(); } RcloneBrowser-1.8.0/src/stream_widget.h000066400000000000000000000007141362250633600201140ustar00rootroot00000000000000#pragma once #include "pch.h" #include "ui_stream_widget.h" class StreamWidget : public QWidget { Q_OBJECT public: StreamWidget(QProcess *rclone, QProcess *player, const QString &remote, const QString &stream, QWidget *parent = nullptr); ~StreamWidget(); public slots: void cancel(); signals: void finished(); void closed(); private: Ui::StreamWidget ui; bool mRunning = true; QProcess *mRclone; QProcess *mPlayer; }; RcloneBrowser-1.8.0/src/stream_widget.ui000066400000000000000000000112571362250633600203060ustar00rootroot00000000000000 StreamWidget 0 0 654 280 Form 0 0 0 0 0 true Qt::ToolButtonTextBesideIcon Qt::RightArrow Stop QToolButton { border: 0 } 0 0 0 true true QToolButton { border: 0 } Show Output true Qt::ToolButtonTextBesideIcon Qt::RightArrow QPlainTextEdit::NoWrap true 0 0 Folder: stream 0 0 Remote: remote showDetails cancel remote stream showOutput output RcloneBrowser-1.8.0/src/transfer_dialog.cpp000066400000000000000000000443531362250633600207630ustar00rootroot00000000000000#include "transfer_dialog.h" #include "list_of_job_options.h" #include "utils.h" TransferDialog::TransferDialog(bool isDownload, bool isDrop, const QString &remote, const QDir &path, bool isFolder, QWidget *parent, JobOptions *task, bool editMode) : QDialog(parent), mIsDownload(isDownload), mIsFolder(isFolder), mIsEditMode(editMode), mJobOptions(task) { ui.setupUi(this); resize(0, 0); setWindowTitle(isDownload ? "Download" : "Upload"); QStyle *style = qApp->style(); ui.buttonSourceFile->setIcon(style->standardIcon(QStyle::SP_FileIcon)); ui.buttonSourceFolder->setIcon(style->standardIcon(QStyle::SP_DirIcon)); ui.buttonDest->setIcon(style->standardIcon(QStyle::SP_DirIcon)); ui.buttonDefaultSource->setIcon(style->standardIcon(QStyle::SP_DirHomeIcon)); ui.buttonDefaultDest->setIcon(style->standardIcon(QStyle::SP_DirHomeIcon)); if (!mIsEditMode) { QPushButton *dryRun = ui.buttonBox->addButton("&Dry run", QDialogButtonBox::AcceptRole); ui.buttonBox->addButton("&Run", QDialogButtonBox::AcceptRole); QObject::connect(dryRun, &QPushButton::clicked, this, [=]() { mDryRun = true; }); } QPushButton *saveTask = ui.buttonBox->addButton( "&Save task", QDialogButtonBox::ButtonRole::ActionRole); QObject::connect( ui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [=]() { ui.cbSyncDelete->setCurrentIndex(0); // set combobox tooltips ui.cbSyncDelete->setItemData(0, "--delete-during", Qt::ToolTipRole); ui.cbSyncDelete->setItemData(1, "--delete-after", Qt::ToolTipRole); ui.cbSyncDelete->setItemData(2, "--delete-before", Qt::ToolTipRole); ui.checkSkipNewer->setChecked(false); ui.checkSkipNewer->setChecked(false); ui.checkSkipExisting->setChecked(false); ui.checkCompare->setChecked(true); ui.cbCompare->setCurrentIndex(0); // set combobox tooltips ui.cbCompare->setItemData(0, "default", Qt::ToolTipRole); ui.cbCompare->setItemData(1, "--checksum", Qt::ToolTipRole); ui.cbCompare->setItemData(2, "--ignore-size", Qt::ToolTipRole); ui.cbCompare->setItemData(3, "--size-only", Qt::ToolTipRole); ui.cbCompare->setItemData(4, "--checksum --ignore-size", Qt::ToolTipRole); // ui.checkVerbose->setChecked(false); ui.checkSameFilesystem->setChecked(false); ui.checkDontUpdateModified->setChecked(false); ui.spinTransfers->setValue(4); ui.spinCheckers->setValue(8); ui.textBandwidth->clear(); ui.textMinSize->clear(); ui.textMinAge->clear(); ui.textMaxAge->clear(); ui.spinMaxDepth->setValue(0); ui.spinConnectTimeout->setValue(60); ui.spinIdleTimeout->setValue(300); ui.spinRetries->setValue(3); ui.spinLowLevelRetries->setValue(10); ui.checkDeleteExcluded->setChecked(false); ui.textExclude->clear(); auto settings = GetSettings(); if (isDownload) { // download ui.textExtra->setText( settings->value("Settings/defaultDownloadOptions").toString()); } else { // upload ui.textExtra->setText( settings->value("Settings/defaultUploadOptions").toString()); } }); ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)->click(); QObject::connect(saveTask, &QPushButton::clicked, this, [=]() { // validate before saving task... if (ui.textDescription->text().isEmpty()) { QMessageBox::warning(this, "Warning", "Please enter task description to Save!"); ui.textDescription->setFocus(Qt::FocusReason::OtherFocusReason); return; } // even though the below does not match the condition on the Run buttons // it SEEMS like blanking either one would be a problem, right? if (ui.textDest->text().isEmpty() || ui.textSource->text().isEmpty()) { QMessageBox::warning(this, "Error", "Invalid Task, source and destination required!"); return; } JobOptions *jobo = getJobOptions(); ListOfJobOptions::getInstance()->Persist(jobo); // always close on save // if (mIsEditMode) this->close(); }); QObject::connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QObject::connect(ui.buttonSourceFile, &QToolButton::clicked, this, [=]() { QString file = QFileDialog::getOpenFileName(this, "Choose file to upload"); if (!file.isEmpty()) { ui.textSource->setText(QDir::toNativeSeparators(file)); ui.textDest->setText(remote + ":" + path.path()); } }); QObject::connect(ui.buttonSourceFolder, &QToolButton::clicked, this, [=]() { auto settings = GetSettings(); QString last_used_source_folder = (settings->value("Settings/lastUsedSourceFolder").toString()); QString folder = QFileDialog::getExistingDirectory( this, "Choose folder to upload", last_used_source_folder, QFileDialog::ShowDirsOnly); if (!folder.isEmpty()) { // store new folder in lastUsedSourceFolder settings->setValue("Settings/lastUsedSourceFolder", folder); ui.textSource->setText(QDir::toNativeSeparators(folder)); ui.textDest->setText(remote + ":" + path.filePath(QFileInfo(folder).fileName())); } }); QObject::connect(ui.buttonDefaultSource, &QToolButton::clicked, this, [=]() { auto settings = GetSettings(); QString default_folder = (settings->value("Settings/defaultUploadDir").toString()); // store default folder in lastUsedSourceFolder settings->setValue("Settings/lastUsedSourceFolder", default_folder); ui.textSource->setText(QDir::toNativeSeparators(default_folder)); if (!default_folder.isEmpty()) { ui.textDest->setText(remote + ":" + path.filePath(QFileInfo(default_folder).fileName())); } else { ui.textDest->setText(remote + ":" + path.path()); }; }); QObject::connect(ui.buttonDest, &QToolButton::clicked, this, [=]() { auto settings = GetSettings(); QString last_used_dest_folder = (settings->value("Settings/lastUsedDestFolder").toString()); QString folder = QFileDialog::getExistingDirectory( this, "Choose destination folder", last_used_dest_folder, QFileDialog::ShowDirsOnly); if (!folder.isEmpty()) { // store new folder in lastUsedDestFolder settings->setValue("Settings/lastUsedDestFolder", folder); if (isFolder) { ui.textDest->setText( QDir::toNativeSeparators(folder + "/" + path.dirName())); } else { ui.textDest->setText(QDir::toNativeSeparators(folder)); } } }); QObject::connect(ui.buttonDefaultDest, &QToolButton::clicked, this, [=]() { auto settings = GetSettings(); QString default_folder = (settings->value("Settings/defaultDownloadDir").toString()); // store default_folder in lastUsedDestFolder settings->setValue("Settings/lastUsedDestFolder", default_folder); if (!default_folder.isEmpty()) { if (isFolder) { ui.textDest->setText( QDir::toNativeSeparators(default_folder + "/" + path.dirName())); } else { ui.textDest->setText(QDir::toNativeSeparators(default_folder)); } } else { ui.textDest->setText(""); }; }); auto settings = GetSettings(); settings->beginGroup("Transfer"); ReadSettings(settings.get(), this); settings->endGroup(); ui.buttonSourceFile->setVisible(!isDownload); ui.buttonSourceFolder->setVisible(!isDownload); ui.buttonDefaultSource->setVisible(!isDownload); ui.buttonDest->setVisible(isDownload); ui.buttonDefaultDest->setVisible(isDownload); // Info only - should not be edited // would be nice to display it only for Google Drive - todo ui.checkisDriveSharedWithMe->setDisabled(true); ui.checkisDriveSharedWithMe->setChecked( settings->value("Settings/driveShared", false).toBool()); // always clear for new jobs ui.textDescription->clear(); if (mIsEditMode && mJobOptions != nullptr) { // it's not really valid for only one of these things to be true. // when operating on an existing instance i.e. a saved task, // changing the dest or src seems to have problems so we // will not allow it. simple enough, and better, to make a // new task for different pairings anyway. that will make // a lot more sense when/if scheduling and history are added... ui.buttonSourceFile->setVisible(false); ui.buttonSourceFolder->setVisible(false); ui.buttonDefaultSource->setVisible(false); ui.buttonDest->setVisible(false); ui.buttonDefaultDest->setVisible(false); ui.textDest->setDisabled(true); ui.textSource->setDisabled(true); putJobOptions(); } else { // set source and destination using defaults if (isDownload) { // download ui.textExtra->setText( settings->value("Settings/defaultDownloadOptions").toString()); ui.textSource->setText(remote + ":" + path.path()); QString folder; QString default_folder = (settings->value("Settings/defaultDownloadDir").toString()); QString last_used_dest_folder = (settings->value("Settings/lastUsedDestFolder").toString()); if (last_used_dest_folder.isEmpty()) { folder = default_folder; } else { folder = last_used_dest_folder; }; if (!folder.isEmpty()) { if (isFolder) { ui.textDest->setText( QDir::toNativeSeparators(folder + "/" + path.dirName())); } else { ui.textDest->setText(QDir::toNativeSeparators(folder)); } } } else { // upload ui.textExtra->setText( settings->value("Settings/defaultUploadOptions").toString()); QString folder; QString default_folder = (settings->value("Settings/defaultUploadDir").toString()); QString last_used_source_folder = (settings->value("Settings/lastUsedSourceFolder").toString()); if (last_used_source_folder.isEmpty()) { folder = default_folder; } else { folder = last_used_source_folder; }; // if upload initiated from drag and drop we dont use default upload // folder if (!isDrop) { ui.textSource->setText(QDir::toNativeSeparators(folder)); if (!folder.isEmpty()) { ui.textDest->setText(remote + ":" + path.filePath(QFileInfo(folder).fileName())); } else { ui.textDest->setText(remote + ":" + path.path()); } } else { // when dropping to root folder if (path.path() == ".") { ui.textDest->setText(remote + ":"); } else { ui.textDest->setText(remote + ":" + path.path()); } }; }; } } TransferDialog::~TransferDialog() { if (result() == QDialog::Accepted) { auto settings = GetSettings(); settings->beginGroup("Transfer"); WriteSettings(settings.get(), this); settings->remove("textSource"); settings->remove("textDest"); settings->endGroup(); } } void TransferDialog::setSource(const QString &path) { ui.textSource->setText(QDir::toNativeSeparators(path)); } QString TransferDialog::getMode() const { if (ui.rbCopy->isChecked()) { return "Copy"; } else if (ui.rbMove->isChecked()) { return "Move"; } else if (ui.rbSync->isChecked()) { return "Sync"; } return QString(); } QString TransferDialog::getSource() const { return ui.textSource->text(); } QString TransferDialog::getDest() const { return ui.textDest->text(); } QStringList TransferDialog::getOptions() { JobOptions *jobo = getJobOptions(); QStringList newWay = jobo->getOptions(); return newWay; } /* * Apply the displayed/edited values on the UI to the * JobOptions object. * * This needs to be edited whenever options are added or changed. */ JobOptions *TransferDialog::getJobOptions() { if (mJobOptions == nullptr) mJobOptions = new JobOptions(mIsDownload); if (ui.rbCopy->isChecked()) { mJobOptions->operation = JobOptions::Copy; mJobOptions->sync = false; } else if (ui.rbMove->isChecked()) { mJobOptions->operation = JobOptions::Move; mJobOptions->sync = false; } else if (ui.rbSync->isChecked()) { mJobOptions->operation = JobOptions::Sync; } mJobOptions->dryRun = mDryRun; ; if (ui.rbSync->isChecked()) { mJobOptions->sync = true; switch (ui.cbSyncDelete->currentIndex()) { case 0: mJobOptions->syncTiming = JobOptions::During; break; case 1: mJobOptions->syncTiming = JobOptions::After; break; case 2: mJobOptions->syncTiming = JobOptions::Before; break; } } mJobOptions->skipNewer = ui.checkSkipNewer->isChecked(); mJobOptions->skipExisting = ui.checkSkipExisting->isChecked(); if (ui.checkCompare->isChecked()) { mJobOptions->compare = true; switch (ui.cbCompare->currentIndex()) { case 0: mJobOptions->compareOption = JobOptions::SizeAndModTime; break; case 1: mJobOptions->compareOption = JobOptions::Checksum; break; case 2: mJobOptions->compareOption = JobOptions::IgnoreSize; break; case 3: mJobOptions->compareOption = JobOptions::SizeOnly; break; case 4: mJobOptions->compareOption = JobOptions::ChecksumIgnoreSize; break; } } else { mJobOptions->compare = false; }; // mJobOptions->verbose = ui.checkVerbose->isChecked(); mJobOptions->sameFilesystem = ui.checkSameFilesystem->isChecked(); mJobOptions->dontUpdateModified = ui.checkDontUpdateModified->isChecked(); mJobOptions->transfers = ui.spinTransfers->text(); mJobOptions->checkers = ui.spinCheckers->text(); mJobOptions->bandwidth = ui.textBandwidth->text(); mJobOptions->minSize = ui.textMinSize->text(); mJobOptions->minAge = ui.textMinAge->text(); mJobOptions->maxAge = ui.textMaxAge->text(); mJobOptions->maxDepth = ui.spinMaxDepth->value(); mJobOptions->connectTimeout = ui.spinConnectTimeout->text(); mJobOptions->idleTimeout = ui.spinIdleTimeout->text(); mJobOptions->retries = ui.spinRetries->text(); mJobOptions->lowLevelRetries = ui.spinLowLevelRetries->text(); mJobOptions->deleteExcluded = ui.checkDeleteExcluded->isChecked(); mJobOptions->excluded = ui.textExclude->toPlainText().trimmed(); mJobOptions->extra = ui.textExtra->text().trimmed(); mJobOptions->source = ui.textSource->text(); mJobOptions->dest = ui.textDest->text(); mJobOptions->isFolder = mIsFolder; mJobOptions->description = ui.textDescription->text(); // auto settings = GetSettings(); // mJobOptions->DriveSharedWithMe = settings->value("Settings/driveShared", // false).toBool(); if (mIsEditMode) mJobOptions->DriveSharedWithMe = ui.checkisDriveSharedWithMe->isChecked(); else { auto settings = GetSettings(); mJobOptions->DriveSharedWithMe = settings->value("Settings/driveShared", false).toBool(); }; return mJobOptions; } /* * Apply the JobOptions object to the displayed widget values. * * It could be "better" to use a two-way binding mechanism, but * if used that should be global to the app; and anyway doing * it this old primitive way makes it easier when the user wants * to not save changes... */ void TransferDialog::putJobOptions() { ui.rbCopy->setChecked(mJobOptions->operation == JobOptions::Copy); ui.rbMove->setChecked(mJobOptions->operation == JobOptions::Move); ui.rbSync->setChecked(mJobOptions->operation == JobOptions::Sync); mDryRun = mJobOptions->dryRun; ui.rbSync->setChecked(mJobOptions->sync); ui.cbSyncDelete->setCurrentIndex((int)mJobOptions->syncTiming); // set combobox tooltips ui.cbSyncDelete->setItemData(0, "--delete-during", Qt::ToolTipRole); ui.cbSyncDelete->setItemData(1, "--delete-after", Qt::ToolTipRole); ui.cbSyncDelete->setItemData(2, "--delete-before", Qt::ToolTipRole); ui.checkSkipNewer->setChecked(mJobOptions->skipNewer); ui.checkSkipExisting->setChecked(mJobOptions->skipExisting); ui.checkCompare->setChecked(mJobOptions->compare); ui.cbCompare->setCurrentIndex(mJobOptions->compareOption); // set combobox tooltips ui.cbCompare->setItemData(0, "default", Qt::ToolTipRole); ui.cbCompare->setItemData(1, "--checksum", Qt::ToolTipRole); ui.cbCompare->setItemData(2, "--ignore-size", Qt::ToolTipRole); ui.cbCompare->setItemData(3, "--size-only", Qt::ToolTipRole); ui.cbCompare->setItemData(4, "--checksum --ignore-size", Qt::ToolTipRole); // ui.checkVerbose->setChecked(mJobOptions->verbose); ui.checkSameFilesystem->setChecked(mJobOptions->sameFilesystem); ui.checkDontUpdateModified->setChecked(mJobOptions->dontUpdateModified); ui.spinTransfers->setValue(mJobOptions->transfers.toInt()); ui.spinCheckers->setValue(mJobOptions->checkers.toInt()); ui.textBandwidth->setText(mJobOptions->bandwidth); ui.textMinSize->setText(mJobOptions->minSize); ui.textMinAge->setText(mJobOptions->minAge); ui.textMaxAge->setText(mJobOptions->maxAge); ui.spinMaxDepth->setValue(mJobOptions->maxDepth); ui.spinConnectTimeout->setValue(mJobOptions->connectTimeout.toInt()); ui.spinIdleTimeout->setValue(mJobOptions->idleTimeout.toInt()); ui.spinRetries->setValue(mJobOptions->retries.toInt()); ui.spinLowLevelRetries->setValue(mJobOptions->lowLevelRetries.toInt()); ui.checkDeleteExcluded->setChecked(mJobOptions->deleteExcluded); ui.textExclude->setPlainText(mJobOptions->excluded); ui.textExtra->setText(mJobOptions->extra); ui.textSource->setText(mJobOptions->source); ui.textDest->setText(mJobOptions->dest); ui.textDescription->setText(mJobOptions->description); // DDBB ui.checkisDriveSharedWithMe->setChecked(mJobOptions->DriveSharedWithMe); } void TransferDialog::done(int r) { if (r == QDialog::Accepted) { if (mIsDownload) { if (ui.textDest->text().isEmpty()) { QMessageBox::warning(this, "Warning", "Please enter destination!"); return; } } else { if (ui.textSource->text().isEmpty()) { QMessageBox::warning(this, "Warning", "Please enter source!"); return; } } } QDialog::done(r); } RcloneBrowser-1.8.0/src/transfer_dialog.h000066400000000000000000000014531362250633600204220ustar00rootroot00000000000000#pragma once #include "job_options.h" #include "pch.h" #include "ui_transfer_dialog.h" class TransferDialog : public QDialog { Q_OBJECT public: TransferDialog(bool isDownload, bool isDrop, const QString &remote, const QDir &path, bool isFolder, QWidget *parent = nullptr, JobOptions *task = nullptr, bool editMode = false); ~TransferDialog(); void setSource(const QString &path); QString getMode() const; QString getSource() const; QString getDest() const; QStringList getOptions(); JobOptions *getJobOptions(); private: Ui::TransferDialog ui; bool mIsDownload; bool mDryRun = false; bool mIsFolder; bool mIsEditMode; JobOptions *mJobOptions; void putJobOptions(); void done(int r) override; signals: void tasksListChanged(); }; RcloneBrowser-1.8.0/src/transfer_dialog.ui000066400000000000000000000702621362250633600206140ustar00rootroot00000000000000 TransferDialog 0 0 765 664 0 0 QLayout::SetFixedSize 0 0 747 0 0 0 Destination: textDest Choose folder QToolButton { border: 0; } Restore default upload folder QToolButton { border: 0; } Choose file QToolButton { border: 0; } 0 0 Source: textSource Choose folder QToolButton { border: 0; } Restore default download folder QToolButton { border: 0; } 0 Settings true --drive-shared-with-me Drive shared with me false --one-file-system Don't cross filesystem boundaries --no-update-modtime Don't update mod-time if files are identical Extra options: Task description Skip files 0 Qt::Horizontal 0 0 0 0 Size & mod-time Size & mod-time Size & checksum Only mod-time Only size Only checksum --update Skip files that are newer on the destination compare logic Compare true --ignore-existing Skip all files that exist Qt::Vertical 0 0 Mode 0 0 0 copy Copy true 0 0 move Move 0 0 sync Sync Qt::Horizontal 0 0 false 0 0 Delete during transfer Delete after transfering Delete before transfering Qt::Vertical 0 0 Qt::Vertical 20 40 Transfer Checkers spinCheckers 0 0 --checkers Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 999 8 Bandwidth (KiB/s or b|k|M|G suffix) textBandwidth 0 0 --bwlimit Minimum size (KiB or b|k|M|G suffix) textMinSize 0 0 --min-size Minimum age (s or ms|s|m|h|d|w|M|y suffix) textMinAge 0 0 --min-age Maximum age (s or ms|s|m|h|d|w|M|y suffix) textMaxAge 0 0 --max-age Maximum depth spinMaxDepth 0 0 --max-depth Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 999999 Connect timeout spinConnectTimeout 0 0 --contimeout Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 999999 60 IO idle timeout spinIdleTimeout 0 0 --timeout Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 999999 300 Retries spinRetries 0 0 --retries Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 999999 3 Low level retries spinLowLevelRetries 0 0 --low-level-retries Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 999999 10 0 0 --transfers Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 999 4 Transfers spinTransfers Exclude --delete-excluded Delete excluded files from destination <html><head/><body><p>every line represents --exclude pattern</p><p>example patterns:</p><p>file.jpg<br/>/pictures/new/*.png<br/>/dir1/dir2/**<br/>&quot;/directory with spaces&quot;</p><p>check rclone online documentation for details.</p><p><br/></p></body></html> QPlainTextEdit::NoWrap QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults textSource buttonSourceFile buttonSourceFolder buttonDefaultSource textDest buttonDest buttonDefaultDest tabWidget rbCopy rbMove rbSync cbSyncDelete checkSkipNewer checkSkipExisting checkCompare cbCompare checkisDriveSharedWithMe checkSameFilesystem checkDontUpdateModified spinTransfers spinCheckers textBandwidth textMinSize textMinAge textMaxAge spinMaxDepth spinConnectTimeout spinIdleTimeout spinRetries spinLowLevelRetries checkDeleteExcluded textExclude rbSync toggled(bool) cbSyncDelete setEnabled(bool) 75 195 232 198 checkCompare toggled(bool) cbCompare setEnabled(bool) 299 198 487 198 RcloneBrowser-1.8.0/src/utils.cpp000066400000000000000000000214021362250633600167460ustar00rootroot00000000000000#include "utils.h" static QString gRclone; static QString gRcloneConf; static QString gRclonePassword; // Software versions comparison // source: https://helloacm.com/how-to-compare-version-numbers-in-c/ std::vector split(const std::string &s, char d) { std::vector r; int j = 0; for (unsigned int i = 0; i < s.length(); i++) { if (s[i] == d) { r.push_back(s.substr(j, i - j)); j = i + 1; } } r.push_back(s.substr(j)); return r; } unsigned int compareVersion(std::string version1, std::string version2) { auto v1 = split(version1, '.'); auto v2 = split(version2, '.'); unsigned int max = v1.size() > v2.size() ? v1.size() : v2.size(); // pad the shorter version string if (v1.size() != max) { for (unsigned int i = max - v1.size(); i--;) { v1.push_back("0"); } } else { for (unsigned int i = max - v2.size(); i--;) { v2.push_back("0"); } } for (unsigned int i = 0; i < max; i++) { unsigned int n1 = stoi(v1[i]); unsigned int n2 = stoi(v2[i]); if (n1 > n2) { // version1 is higher than version2 return 1; } else if (n1 < n2) { // version2 is higher than version1 return 2; } } // the same versions return 0; } static QString GetIniFilename() { #ifdef Q_OS_MACOS QFileInfo applicationPath = qApp->applicationFilePath(); // qDebug() << QString(applicationPath.absolutePath()); // on macOS excecutable file is located in // ./rclone-browser.app/Contents/MasOS/ to get actual bundle folder we have to // traverse three levels up QFileInfo MacOSPath = applicationPath.dir().path(); QFileInfo ContentsPath = MacOSPath.dir().path(); QFileInfo appBundlePath = ContentsPath.dir().path(); // qDebug() << QString("utils.cpp appBundle.absolutePath: " + // appBundlePath.absolutePath()); // qDebug() << QString( // "utils.cpp ini file:" + // appBundlePath.dir().filePath(appBundlePath.baseName() + ".ini")); return appBundlePath.dir().filePath(appBundlePath.baseName() + ".ini"); #else #ifdef Q_OS_WIN QFileInfo applicationPath = qApp->applicationFilePath(); return applicationPath.dir().filePath(applicationPath.baseName() + ".ini"); #else QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); return xdg_config_home + "/rclone-browser/rclone-browser.ini"; #endif #endif } bool IsPortableMode() { QString ini = GetIniFilename(); QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); // qDebug() << QString("utils.cpp $XDG_CONFIG_HOME: " + xdg_config_home); QString appimage = qgetenv("APPIMAGE"); // qDebug() << QString("utils.cpp $APPIMAGE: " + appimage); // cat ".config" from $XDG_CONFIG_HOME // it should be the same as appimage if run from AppImage xdg_config_home = xdg_config_home.left(xdg_config_home.length() - 7); // qDebug() << QString("utils.cpp $XDG_CONFIG_HOME-7: " + xdg_config_home); if (!xdg_config_home.isEmpty() && !appimage.isEmpty() && xdg_config_home == appimage) { return true; } if (QFileInfo(ini).exists()) { return true; } else { return false; } // return QFileInfo(ini).exists(); } std::unique_ptr GetSettings() { if (IsPortableMode()) { return std::unique_ptr( new QSettings(GetIniFilename(), QSettings::IniFormat)); } return std::unique_ptr(new QSettings); } void ReadSettings(QSettings *settings, QObject *widget) { QString name = widget->objectName(); if (!name.isEmpty() && settings->contains(name)) { if (QRadioButton *obj = qobject_cast(widget)) { obj->setChecked(settings->value(name).toBool()); return; } if (QCheckBox *obj = qobject_cast(widget)) { obj->setChecked(settings->value(name).toBool()); return; } if (QComboBox *obj = qobject_cast(widget)) { obj->setCurrentIndex(settings->value(name).toInt()); return; } if (QSpinBox *obj = qobject_cast(widget)) { obj->setValue(settings->value(name).toInt()); return; } if (QLineEdit *obj = qobject_cast(widget)) { obj->setText(settings->value(name).toString()); return; } if (QPlainTextEdit *obj = qobject_cast(widget)) { int count = settings->beginReadArray(name); QStringList lines; lines.reserve(count); for (int i = 0; i < count; i++) { settings->setArrayIndex(i); lines.append(settings->value("value").toString()); } settings->endArray(); obj->setPlainText(lines.join('\n')); return; } } for (auto child : widget->children()) { ReadSettings(settings, child); } } void WriteSettings(QSettings *settings, QObject *widget) { QString name = widget->objectName(); if (QCheckBox *obj = qobject_cast(widget)) { settings->setValue(name, obj->isChecked()); return; } if (QComboBox *obj = qobject_cast(widget)) { settings->setValue(name, obj->currentIndex()); return; } if (QSpinBox *obj = qobject_cast(widget)) { settings->setValue(name, obj->value()); return; } if (QLineEdit *obj = qobject_cast(widget)) { if (obj->text().isEmpty()) { settings->remove(name); } else { settings->setValue(name, obj->text()); } return; } if (QPlainTextEdit *obj = qobject_cast(widget)) { QString text = obj->toPlainText().trimmed(); if (!text.isEmpty()) { QStringList lines = text.split('\n'); settings->beginWriteArray(name, lines.size()); for (int i = 0; i < lines.count(); i++) { settings->setArrayIndex(i); settings->setValue("value", lines[i]); } settings->endArray(); } return; } for (auto child : widget->children()) { WriteSettings(settings, child); } } QStringList GetRcloneConf() { if (gRcloneConf.isEmpty()) { return QStringList(); } QString conf = gRcloneConf; if (IsPortableMode() && QFileInfo(conf).isRelative()) { #ifdef Q_OS_MACOS // on macOS excecutable file is located in // ./rclone-browser.app/Contents/MasOS/rclone-browser to get actual bundle // folder we have to traverse three levels up conf = QDir(qApp->applicationDirPath() + "/../../..").filePath(conf); #else #ifdef Q_OS_WIN conf = QDir(qApp->applicationDirPath()).filePath(conf); #else QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); conf = QDir(xdg_config_home + "/..").filePath(conf); #endif #endif // qDebug() << QString("utils.cpp conf: " + conf); } return QStringList() << "--config" << conf; } void SetRcloneConf(const QString &rcloneConf) { gRcloneConf = rcloneConf; } QString GetRclone() { QString rclone = gRclone; if (IsPortableMode() && QFileInfo(rclone).isRelative()) { #ifdef Q_OS_MACOS // on macOS excecutable file is located in // ./rclone-browser.app/Contents/MasOS/rclone-browser to get actual bundle // folder we have to traverse three levels up rclone = QDir(qApp->applicationDirPath() + "/../../..").filePath(rclone); #else #ifdef Q_OS_WIN rclone = QDir(qApp->applicationDirPath()).filePath(rclone); #else QString xdg_config_home = qgetenv("XDG_CONFIG_HOME"); rclone = QDir(xdg_config_home + "/..").filePath(rclone); #endif #endif // qDebug() << QString("utils.cpp rclone portable: " + rclone); } return rclone; } void SetRclone(const QString &rclone) { gRclone = rclone.trimmed(); } void UseRclonePassword(QProcess *process) { if (!gRclonePassword.isEmpty()) { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("RCLONE_CONFIG_PASS", gRclonePassword); process->setProcessEnvironment(env); } } void SetRclonePassword(const QString &rclonePassword) { gRclonePassword = rclonePassword; } QStringList GetDriveSharedWithMe() { auto settings = GetSettings(); bool driveShared = settings->value("Settings/driveShared", false).toBool(); QStringList driveSharedOption; if (driveShared) { driveSharedOption << "--drive-shared-with-me"; } return driveSharedOption; } QStringList GetDefaultRcloneOptionsList() { auto settings = GetSettings(); QString defaultRcloneOptions = settings->value("Settings/defaultRcloneOptions").toString(); QStringList defaultRcloneOptionsList; if (!defaultRcloneOptions.isEmpty()) { for (auto arg : defaultRcloneOptions.split(' ')) { defaultRcloneOptionsList << arg; } } return defaultRcloneOptionsList; } QStringList GetShowHidden() { auto settings = GetSettings(); bool showHidden = settings->value("Settings/showHidden", true).toBool(); QStringList showHiddenOption; if (!showHidden) { showHiddenOption << "--exclude" << ".*/**" << "--exclude" << ".*"; } return showHiddenOption; } RcloneBrowser-1.8.0/src/utils.h000066400000000000000000000011501362250633600164110ustar00rootroot00000000000000#pragma once #include "pch.h" std::unique_ptr GetSettings(); void ReadSettings(QSettings *settings, QObject *widget); void WriteSettings(QSettings *settings, QObject *widget); bool IsPortableMode(); QString GetRclone(); void SetRclone(const QString &rclone); QStringList GetRcloneConf(); void SetRcloneConf(const QString &rcloneConf); void UseRclonePassword(QProcess *process); void SetRclonePassword(const QString &rclonePassword); QStringList GetDriveSharedWithMe(); QStringList GetDefaultRcloneOptionsList(); QStringList GetShowHidden(); unsigned int compareVersion(std::string, std::string);