├── README.md ├── icons ├── back.png ├── browse.png ├── cloud-square.png ├── cloud.png ├── cloud.svg ├── download.png ├── dropbox-24.png ├── dropbox.png ├── eject.png ├── folder.png ├── gdrive-24.png ├── gdrive.png ├── link.png ├── mount.png ├── move.png ├── new.png ├── none-24.png ├── onedrive-24.png ├── onedrive.png ├── other-24.png ├── remove.png ├── stop.png ├── trash.png ├── upload.png └── webinterface.png ├── main.sh └── terminal-run /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![program icon](https://raw.githubusercontent.com/Botspot/cloudbuddy/main/icons/cloud.png) 3 | # CloudBuddy 4 | Bash-based GUI client for [`rclone`](https://rclone.org/). This was written by Botspot on 7/29/2021. 5 | ![2021-07-28-204821_1366x768_scrot](https://user-images.githubusercontent.com/54716352/127436113-56ac9a1d-2bc5-4812-b927-82d16b80565d.png) 6 | 7 | ## Installation: 8 | [![badge](https://github.com/Botspot/pi-apps/blob/master/icons/badge.png?raw=true)](https://github.com/Botspot/pi-apps) 9 | ### Or, to download manually: 10 | ``` 11 | git clone https://github.com/Botspot/cloudbuddy 12 | ``` 13 | CloudBuddy is portable and can be executed from anywhere on your filesystem. For simplicity, this README will assume CloudBuddy is located in your `$HOME` directory. 14 | ## First run: 15 | ``` 16 | ~/cloudbuddy/main.sh 17 | ``` 18 | When running for the first time, CloudBuddy will: 19 | - Check and install **dependencies**: `yad`, `xclip`, `rclone`. Note: If `rclone` is already installed with **`apt`**, CloudBuddy will overwrite it with the latest version of [`rclone` from source](https://rclone.org/downloads/). 20 | - Add a **Main menu launcher**. (`~/.local/share/applications/cloudbuddy.desktop`) This launcher is located under the **Internet** category. 21 | - Make sure the "**`fuse`**" kernel module has been loaded so that `rclone` can mount drives. If fuse is not loaded, CloudBuddy will attempt to load it by running `sudo modprobe fuse`. If that fails, CloudBuddy's mounting capabilities will be disabled and hidden. 22 | - **Check for updates**. If the last local commit and the latest online commit do not match, CloudBuddy will run `git pull` and refresh the script if it was modified. Note: If you make a fork of this repository, you should change the github URL in the script to point to your repository. To disable update-checking, create a file at: `~/cloudbuddy/no-update`. 23 | 24 | ## Usage 25 | Note: CloudBuddy is intended to be **self-explanatory**. Everything should proceed in a logical fashon and nearly all users will have no problem using this program. 26 | But for those few users who wish to get acquainted with CloudBuddy before they try it out, **here you go.** 27 | ## Connecting to a new cloud drive 28 | This automates the [`rclone config`](https://rclone.org/commands/rclone_config/) process. Feel free to manually run `rclone config` for more options and a wider selection of cloud storage providers. 29 | 1. In the main window, click this button: 30 | ![Screenshot from 2021-07-30 09-28-46](https://user-images.githubusercontent.com/54716352/127683887-ddb82be7-6db6-4f73-a0f0-9bb9b27fff4b.png) 31 | 2. Choose a name for the new cloud drive, and select which type of drive it is. 32 | ![Screenshot from 2021-07-30 09-35-38](https://user-images.githubusercontent.com/54716352/127683918-b338f9ba-9973-4636-9ac8-6c840c6894ac.png) 33 | 3. Within seconds, a web browser will appear for authorization. 34 | ![2021-07-30-095030_1366x768_scrot](https://user-images.githubusercontent.com/54716352/127684335-7ab585ff-b2ff-45ea-9505-f64110e98a57.png) 35 | 4. That's it! 36 | ## Disconnecting from a cloud drive 37 | 1. In the main window, click this button: 38 | ![Screenshot from 2021-07-30 10-04-22](https://user-images.githubusercontent.com/54716352/127684538-d8026d21-ce9f-43dd-b101-3cbe4e354e5d.png) 39 | 2. Choose a cloud drive to sign out of. 40 | ![Screenshot from 2021-07-30 10-05-22](https://user-images.githubusercontent.com/54716352/127712410-aa9c1272-df5c-46c7-a787-3e3234a5c088.png) 41 | 3. That's it! First, this uses [`rclone config disconnect`](https://rclone.org/commands/rclone_config_disconnect/) to ask the cloud to revoke rclone's API key, then deletes the cloud drive from `rclone`'s config file using [`rclone config delete`](https://rclone.org/commands/rclone_config_delete/). 42 | ## Mounting a drive 43 | This uses [rclone's `mount` command](https://rclone.org/commands/rclone_mount/) to view your cloud drive as if it was an external data disk. 44 | 1. In the main window, click this button: 45 | ![Screenshot from 2021-07-30 10-12-08](https://user-images.githubusercontent.com/54716352/127685235-1efae846-d9ec-4203-b01b-f6e41b83791e.png) 46 | 2. Select a drive to continue. 47 | ![Screenshot from 2021-07-30 10-17-07](https://user-images.githubusercontent.com/54716352/127712269-991703a1-d8f7-43d1-901d-d191f254cbd1.png) 48 | 3. Choose an empty directory to mount the cloud drive to. 49 | ![Screenshot from 2021-07-30 10-22-20](https://user-images.githubusercontent.com/54716352/127685584-86ee15fc-6d2f-4d56-b43c-5d30ea9fb9d5.png) 50 | 4. Wait for it... 51 | ![2021-07-30-103150_1366x768_scrot](https://user-images.githubusercontent.com/54716352/127685725-bfc3eb94-7236-41e5-aa22-4c8a262bfbbf.png) 52 | 5. Done! 53 | ## Unmounting a drive 54 | 1. In the main window, click this button: 55 | ![Screenshot from 2021-07-30 10-35-26](https://user-images.githubusercontent.com/54716352/127685787-d97a4c2a-27e5-419e-ad43-2c1ae822fd62.png) 56 | 2. Choose a drive to be unmounted. 57 | ![Screenshot from 2021-07-30 10-39-19](https://user-images.githubusercontent.com/54716352/127686225-93ad674d-478f-4fed-918d-3add6a0a1bdc.png) 58 | 3. Done! 59 | ## Browsing a drive 60 | 1. In the main window, click this button: 61 | ![Screenshot from 2021-07-30 10-41-39](https://user-images.githubusercontent.com/54716352/127687406-6382a4e8-6658-467e-abb2-688068ef0727.png) 62 | 2. Select a drive to continue. 63 | ![Screenshot from 2021-07-30 10-17-07](https://user-images.githubusercontent.com/54716352/127712269-991703a1-d8f7-43d1-901d-d191f254cbd1.png) 64 | 3. This is CloudBuddy's built-in file browser. It exclusively uses rclone commands for navigation and all operations are performed server-side. 65 | ![Screenshot from 2021-07-30 10-45-49](https://user-images.githubusercontent.com/54716352/127714250-17785459-cedc-4fdc-9e9b-0cf79de1357a.png) 66 | The file browser is capable of: 67 | - **Uploading** files or folders with this handy drag-n-drop window: 68 | ![Screenshot from 2021-07-30 11-04-10](https://user-images.githubusercontent.com/54716352/127687058-53d6b01c-b84a-4055-9805-fe83df54ea42.png) 69 | - **Downloading** the selected file(s) or folder(s) from the list to $HOME/Downloads. This takes advantage of [`rclone copy`](https://rclone.org/commands/rclone_copy/). Just look at that pretty terminal output! 70 | ![2021-07-30-121153_1366x768_scrot](https://user-images.githubusercontent.com/54716352/127688484-5a97dafc-7561-456b-a26a-ca0d5c2a9cd9.png) 71 | - **Moving** or **Renaming** an item. This uses [the `rclone moveto` command](https://rclone.org/commands/rclone_moveto/) - it makes an API call to the cloud drive to perform the operation so no large downloads or uploads have to occur. 72 | ![Screenshot from 2021-07-30 12-17-19](https://user-images.githubusercontent.com/54716352/127690463-465ee964-08e4-427a-9798-61d3ce6fe069.png) 73 | - **Deleting** the selected file(s) or folder(s) from the cloud drive. This uses [`rclone purge`](https://rclone.org/commands/rclone_purge/) or [`rclone deletefile`](https://rclone.org/commands/rclone_deletefile/) commands. Keep in mind that most cloud drives will keep deleted items in a recovery folder for a while. 74 | - **Creating a publically sharable link** to the selected file or folder. 75 | ![Screenshot from 2021-07-30 12-40-19](https://user-images.githubusercontent.com/54716352/127691403-3da765ae-9fc1-47d2-bd49-3f32876986f6.png) 76 | Notice the above **Copy** button. Because it uses `xclip` to copy the URL, CloudBuddy treats `xclip` as a required dependency. 77 | ## Web Interface 78 | This simple button runs [rclone's built-in browser interface](https://rclone.org/gui/). 79 | 1. In the main window, click this button: 80 | ![Screenshot from 2021-07-30 20-51-07](https://user-images.githubusercontent.com/54716352/127725151-a9b37210-5d39-431f-8fd0-b7d1671a62ba.png) 81 | 2. A web browser should open with rclone's HTML-based interface. 82 | Fun fact: I (Botspot, the maker of CloudBuddy) was not aware rclone had this feature until *after* CloudBuddy was mostly complete. 83 | 84 | And with that, the GUI usage tutorial comes to an end. If CloudBuddy was designed correctly, you didn't have to read this to use CloudBuddy. 85 | 86 | ## Command-line options 87 | CloudBuddy is run by a single bash ***megascript***. It's not that long a script though, so why do I call it called a *mega*script? 88 | Because CloudBuddy's `main.sh` script is actually **many bash scripts in one**. 89 | When you launch CloudBuddy, it will start at the top of the script, run [a little preliminary stuff](https://github.com/Botspot/cloudbuddy#first-run) like update-checking, then ***skip*** nearly all the way to the end of the script and launch the main choice window. When you click a button, *that window will run another background instance* of CloudBuddy's `main.sh` with a command-line flag to preserve which button you clicked. 90 | This approach has its downsides, but it is necessary for the main window to have a button layout of **3-by-2**. Otherwise, all 6 buttons would be in one long row - which would look horrible and would prevent additional buttons from being added due to space constraints. 91 | Did I mention that all CloudBuddy windows use **`yad`**? This dialog box utility is the most robust GTK dialog available for bash scripting, and is the basis of [Pi-Apps](https://github.com/Botspot/pi-apps), [Pi Power Tools](https://github.com/Botspot/Pi-Power-Tools), [YouTubuddy](https://github.com/Botspot/youtubuddy), [Update Buddy](https://github.com/Botspot/update-buddy), [The TwisterOS Patcher](https://github.com/Botspot/TwistUP), and [Windows Screensavers for RPi](https://github.com/Botspot/Screensavers). YAD is not perfect though, and its inability to display multiple rows of exit-code-type buttons is a major hindrance. 92 | **Enough said. Command-line options for CloudBuddy are below.** 93 | PRO TIP: if you run CloudBuddy in a terminal, it will dynamically generate a custom-tailored command to instantly reach the exact same place later. For example, if I was using the file browser and wanted to quickly reach a deep subdirectory later, this information would be very helpful: 94 | ![2021-07-30-133534_1366x768_scrot](https://user-images.githubusercontent.com/54716352/127697304-cdffa962-d336-4e66-9cc7-1bfdf5ae2d50.png) 95 | #### Set up CloudBuddy and then exit 96 | On every launch, CloudBuddy checks for dependencies and creates a menu launcher. To run this preliminary stuff and then exit immediately, use this command-line flag: 97 | ``` 98 | ~/cloudbuddy/main.sh setup 99 | ``` 100 | #### Source CloudBuddy's functions and then exit 101 | Necessary for when CloudBuddy is downloading or uploading something in a terminal and we want to reuse CloudBuddy's colorized-`echo` functions. 102 | ``` 103 | source ~/cloudbuddy/main.sh source 104 | ``` 105 | More functions may be added in the future, but at the time of writing this they are: `error`, `warning`, `echobright`, `echocommand`, `echoprogress`, `list_descendants`, `back`, `drivetype`, and `choosedrive`. 106 | #### New drive 107 | ``` 108 | ~/cloudbuddy/main.sh newdrive 109 | ``` 110 | Optionally, a drive name and drive type can be specified (in that order) on the command-line to skip launching the selection window. 111 | #### Remove drive 112 | ``` 113 | ~/cloudbuddy/main.sh removedrive 114 | ``` 115 | Optionally, a drive name can be specified on the command-line to skip launching the selection window. 116 | #### Mount drive 117 | ``` 118 | ~/cloudbuddy/main.sh mountdrive 119 | ``` 120 | Optionally, a drive name and mountpoint can be specified (in that order) on the command-line to skip launching the selection window. 121 | #### Unmount drive 122 | ``` 123 | ~/cloudbuddy/main.sh unmountdrive 124 | ``` 125 | Optionally, a drive name, OR a mountpoint can be specified on the command-line to skip launching the selection window. 126 | #### Browse drive 127 | ``` 128 | ~/cloudbuddy/main.sh browsedrive 129 | ``` 130 | Optionally, a drive can be specified on the command-line to skip launching the selection window. Additionally, you can specify a subfolder to begin in, like this: 131 | ``` 132 | ~/cloudbuddy/main.sh browsedrive "My Google Drive:Attachments/old stuff" 133 | ``` 134 | Additionally, a simpler, faster file browser can be launched if the `fastmode` variable is set to **`1`**, like this: 135 | ``` 136 | fastmode=1 ~/cloudbuddy/main.sh browsedrive 137 | ``` 138 | #### Web Interface 139 | ``` 140 | ~/cloudbuddy/main.sh webinterface 141 | ``` 142 | -------------------------------------------------------------------------------- /icons/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/back.png -------------------------------------------------------------------------------- /icons/browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/browse.png -------------------------------------------------------------------------------- /icons/cloud-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/cloud-square.png -------------------------------------------------------------------------------- /icons/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/cloud.png -------------------------------------------------------------------------------- /icons/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | cloud 64 | 65 | 66 | 67 | 68 | components 69 | 70 | 71 | 72 | 73 | 74 | 75 | outline 76 | 77 | 78 | eye L 79 | 80 | 81 | eye R 82 | 83 | -------------------------------------------------------------------------------- /icons/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/download.png -------------------------------------------------------------------------------- /icons/dropbox-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/dropbox-24.png -------------------------------------------------------------------------------- /icons/dropbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/dropbox.png -------------------------------------------------------------------------------- /icons/eject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/eject.png -------------------------------------------------------------------------------- /icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/folder.png -------------------------------------------------------------------------------- /icons/gdrive-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/gdrive-24.png -------------------------------------------------------------------------------- /icons/gdrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/gdrive.png -------------------------------------------------------------------------------- /icons/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/link.png -------------------------------------------------------------------------------- /icons/mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/mount.png -------------------------------------------------------------------------------- /icons/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/move.png -------------------------------------------------------------------------------- /icons/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/new.png -------------------------------------------------------------------------------- /icons/none-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/none-24.png -------------------------------------------------------------------------------- /icons/onedrive-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/onedrive-24.png -------------------------------------------------------------------------------- /icons/onedrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/onedrive.png -------------------------------------------------------------------------------- /icons/other-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/other-24.png -------------------------------------------------------------------------------- /icons/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/remove.png -------------------------------------------------------------------------------- /icons/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/stop.png -------------------------------------------------------------------------------- /icons/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/trash.png -------------------------------------------------------------------------------- /icons/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/upload.png -------------------------------------------------------------------------------- /icons/webinterface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4e8fdc73e5f505102837880b4ce9b352cc68c6aa/icons/webinterface.png -------------------------------------------------------------------------------- /main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -a 4 | DIRECTORY="$(readlink -f "$(dirname "$0")")" 5 | 6 | error() { 7 | echo -e "\e[91m$1\e[39m" | sed 's||| ; |||g' 1>&2 8 | zenity --error --width 300 --text "$(echo -e "$1" | sed 's/&/&/g')" 9 | kill $(cat "${DIRECTORY}/mypid") $(list_descendants $(cat "${DIRECTORY}/mypid")) 2>/dev/null 10 | exit 1 11 | } 12 | 13 | errorback() { 14 | echo -e "\e[91m$1\e[39m" | sed 's|||g' | sed 's|||g' 1>&2 15 | zenity --error --width 300 --text "$(echo -e "$1" | sed 's/&/&/g' | tr -d '<>')" 16 | back 17 | } 18 | 19 | warning() { #just like error, but doesn't exit 20 | echo -e "\e[91m$1\e[39m" | sed 's|||g ; s|||g' 1>&2 21 | zenity --error --width 300 --text "$(echo -e "$1" | sed 's/&/&/g')" 22 | } 23 | 24 | #Displays something meaningful to the user that is brighter than normal output 25 | echobright() { 26 | echo -e "\e[97m$@\e[39m" 27 | } 28 | 29 | #displays a command CloudBuddy is running. 30 | echocommand() { 31 | echo -e "\e[32mRunning this command: \e[96m$@\e[39m" 32 | } 33 | 34 | #Yellow text with clock to display things like "Creating new drive..." 35 | echoprogress() { 36 | echo -e "\e[33m🕑 $@\e[39m" 37 | } 38 | 39 | list_descendants() { #list descent PIDs to the given parent PID. 40 | #This is used to kill yad when a parent script is killed. 41 | #https://unix.stackexchange.com/a/124148/369481 42 | local children=$(ps -o pid= --ppid "$1") 43 | 44 | for pid in $children 45 | do 46 | list_descendants "$pid" 47 | done 48 | 49 | echo "$children" 50 | } 51 | 52 | back() { #handy function to go back to the main start page 53 | "$0" & 54 | kill -s SIGHUP $$ 55 | exit 0 56 | } 57 | 58 | drivetype() { #determine the type of a $1-specified drive. 'gdrive', 'dropbox', 'onedrive' 59 | [ -z "$1" ] && error "drivetype(): requires an argument" 60 | 61 | #ordinary rclone config file location: $HOME/.config/rclone/rclone.conf 62 | if [ ! -f "$(rclone config file | sed -n 2p)" ];then 63 | warning "drivetype(): rclone config file not found!\nIt is: $(rclone config file)" 1>&2 64 | echo other 65 | return 0 66 | fi 67 | 68 | drivetype="$(cat "$HOME/.config/rclone/rclone.conf" | grep -x "\[$1\]" --after=1 | sed -n 2p | sed 's/type = //g' | \ 69 | sed 's/^drive$/gdrive/g')" 70 | 71 | #rename drivetype variable to 'other' if unknown 72 | if [ "$drivetype" != gdrive ] && [ "$drivetype" != onedrive ] && [ "$drivetype" != dropbox ];then 73 | drivetype=other 74 | fi 75 | echo "$drivetype" 76 | } 77 | 78 | choosedrive() { #gui selector for drives 79 | #Variable 1 is the text prompt, if set. 80 | useicons=1 81 | LIST='' 82 | if [ ! -z "$useicons" ] && [ "$useicons" == 1 ];then 83 | IFS=$'\n' 84 | for drive in $(echo "$remotes" | sed 's/:$//g') ;do 85 | LIST="$LIST 86 | ${DIRECTORY}/icons/$(drivetype "$drive")-24.png 87 | $drive" 88 | done 89 | LIST="$(echo "$LIST" | grep .)" 90 | #echo "$LIST" 91 | 92 | echo "$LIST" | yad "${yadflags[@]}" --list \ 93 | --width=260 --height=300 \ 94 | --column=':IMG' --column='' --print-column=2 --no-headers --text="$([ -z "$1" ] && echo "Select a drive to continue." || echo "$1")" 95 | else #no icons in list - faster 96 | echo "$remotes" | sed 's/:$//g' | yad "${yadflags[@]}" --list \ 97 | --width=260 --height=300 \ 98 | --column='' --no-headers --text="Select a drive to continue." 99 | fi 100 | } 101 | 102 | #get OS architecture (32-bit or 64-bit) 103 | if [ "$(od -An -t x1 -j 4 -N 1 "$(readlink -f /sbin/init)")" = ' 02' ];then 104 | arch=64 105 | elif [ "$(od -An -t x1 -j 4 -N 1 "$(readlink -f /sbin/init)")" = ' 01' ];then 106 | arch=32 107 | else 108 | error "Failed to detect OS CPU architecture! Something is very wrong." 109 | fi 110 | 111 | [ "$1" == source ] && return 0 #this script's functions can be sourced, then immediately exit, if you run 'main.sh source' 112 | #END OF FUNCTIONS, BEGINNING OF SCRIPT 113 | 114 | #kill other instances of this script. Necessary for yad's --form buttons as they spawn child processes 115 | if [ -f "${DIRECTORY}/mypid" ] ;then 116 | #echo -e "parent: $(cat "${DIRECTORY}/mypid")\nchildren: $(list_descendants $(cat "${DIRECTORY}/mypid"))" 117 | kill -s SIGHUP $(cat "${DIRECTORY}/mypid") $(list_descendants $(cat "${DIRECTORY}/mypid")) 2>/dev/null 118 | fi 119 | mypid=$$ 120 | echo $mypid > "${DIRECTORY}/mypid" 121 | 122 | #check for updates and auto-update if the no-update files does not exist 123 | if [ ! -f "${DIRECTORY}/no-update" ];then 124 | prepwd="$(pwd)" 125 | cd "$DIRECTORY" 126 | localhash="$(git rev-parse HEAD)" 127 | latesthash="$(git ls-remote https://github.com/Botspot/cloudbuddy HEAD | awk '{print $1}')" 128 | 129 | if [ "$localhash" != "$latesthash" ] && [ ! -z "$latesthash" ] && [ ! -z "$localhash" ];then 130 | echobright "CloudBuddy is out of date. Downloading new version..." 131 | echobright "To prevent update checking from now on, create a file at ${DIRECTORY}/no-update" 132 | sleep 1 133 | 134 | #get file hash of this running script to compare it later 135 | oldhash="$(shasum "$0")" 136 | 137 | echobright "running 'git pull'..." 138 | git pull 139 | 140 | if [ "$oldhash" == "$(shasum "$0")" ];then 141 | #this script not modified by git pull 142 | echobright "git pull finished. Proceeding..." 143 | else 144 | echobright "git pull finished. Reloading script..." 145 | #run updated script in background 146 | ( "$0" "$@" ) & 147 | exit 0 148 | fi 149 | fi 150 | cd "$prepwd" 151 | fi 152 | 153 | #install dependencies 154 | command -v yad >/dev/null || (echobright "Installing 'yad'..." ; sudo apt update && sudo apt install -y yad || error "Failed to install yad!") 155 | command -v xclip >/dev/null || (echobright "Installing 'xclip'..." ; sudo apt update && sudo apt install -y xclip || error "Failed to install xclip!") 156 | #rclone installation - if rclone command missing ------------ or if armhf rclone is installed on a 64-bit system ------------------------------- or if installed version is out of date 157 | if ! command -v rclone >/dev/null || ([ "$arch" == 64 ] && [ "$(od -An -t x1 -j 4 -N 1 /usr/bin/rclone)" == ' 01' ]) || (version="$(cat /tmp/rclone-version >/dev/null || wget -O /tmp/rclone-version https://downloads.rclone.org/version.txt && cat /tmp/rclone-version)" && [ -s /tmp/rclone-version ] && [ "$(rclone --version | head -n1)" != "$version" ]);then 158 | echobright "Installing 'rclone'..." 159 | 160 | if [ "$(uname -m)" == x86_64 ];then 161 | archname=amd64 162 | elif [ "$(uname -m)" == i686 ];then 163 | archname=386 164 | else 165 | archname=arm 166 | if [ "$arch" == 64 ];then 167 | archname=arm64 168 | fi 169 | fi 170 | echocommand "wget https://downloads.rclone.org/rclone-current-linux-$archname.zip -O rclone-current-linux-$archname.zip" 171 | wget https://downloads.rclone.org/rclone-current-linux-$archname.zip -O rclone-current-linux-$archname.zip || error "Failed to download rclone from downloads.rclone.org!" 172 | 173 | echocommand "unzip -j -o -d rclone-temp rclone-current-linux-$archname.zip" 174 | unzip -j -o -d rclone-temp rclone-current-linux-$archname.zip || error "Failed to extract rclone-current-linux-$archname.zip" 175 | 176 | echocommand "sudo mv rclone-temp/rclone /usr/bin/rclone" 177 | sudo mv -f rclone-temp/rclone /usr/bin/rclone || error "Failed to move rclone binary to /usr/bin/rclone" 178 | echobright "sudo mv rclone-temp/rclone.1 /usr/share/man/man1/rclone.1" 179 | sudo mv -f rclone-temp/rclone.1 /usr/share/man/man1/rclone.1 180 | 181 | echocommand "sudo chown root: /usr/bin/rclone" 182 | sudo chown root: /usr/bin/rclone 183 | echocommand "rm -rf rclone-current-linux-$archname.zip rclone-temp" 184 | rm -rf rclone-current-linux-$archname.zip rclone-temp 185 | echobright "Rclone installation successful. Proceeding..." 186 | fi 187 | 188 | #check for fuse kernel module. Launch it if necessary. If still not running, set usefuse var to zero. 189 | usefuse=1 190 | if ! ls /sys/module | awk '{print $1}' | grep -qFx fuse ;then 191 | echobright "Kernel module 'fuse' not found. Attempting to load it..." 192 | echocommand "sudo modprobe fuse" 193 | sudo modprobe fuse 194 | 195 | #check again to ensure fuse module running 196 | if ! ls /sys/module | awk '{print $1}' | grep -qFx fuse ;then 197 | usefuse=0 198 | warning "Failed to load kernel module 'fuse'!\nAs a result, rclone cannot mount cloud drives." 199 | fi 200 | fi 201 | 202 | #menu button 203 | if [ ! -f ~/.local/share/applications/cloudbuddy.desktop ];then 204 | echobright "Creating menu button..." 205 | mkdir -p ~/.local/share/applications 206 | echo "[Desktop Entry] 207 | Name=CloudBuddy 208 | Comment=The ultimate cloud storage manager. 209 | Exec=$0 210 | Icon=${DIRECTORY}/icons/cloud-square.png 211 | Terminal=false 212 | Type=Application 213 | Categories=Application;Network;RemoteAccess; 214 | StartupNotify=true" > ~/.local/share/applications/cloudbuddy.desktop 215 | fi 216 | 217 | #icon 218 | if [ ! -f ~/.local/share/icons/cloudbuddy.png ];then 219 | echobright "copying program icon..." 220 | mkdir -p ~/.local/share/icons 221 | cp ${DIRECTORY}/icons/cloud-square.png ~/.local/share/icons/cloudbuddy.png 222 | fi 223 | 224 | if [ "$1" == setup ];then 225 | echo "CloudBuddy setup complete." 226 | exit 0 227 | fi 228 | 229 | if [ -d /dev/shm ];then 230 | #if linux kernel's RAM disk directory exists, use it for signaling 231 | useshm=1 232 | else 233 | useshm=0 234 | fi 235 | 236 | yadflags=(--center --title="CloudBuddy" --separator='\n' --window-icon="${DIRECTORY}/icons/cloud-square.png" --class CloudBuddy --name CloudBuddy) 237 | 238 | #generate list of remotes that rclone currently knows about 239 | remotes="$(rclone listremotes)" 240 | 241 | #COMMAND-LINE SUBSCRIPTS 242 | if [ "$1" == newdrive ];then 243 | #Usage: main.sh newdrive driveName drivetype 244 | echobright "New drive" 245 | drive="$3" 246 | drivetype="$2" 247 | 248 | while [ -z "$drive" ];do #skip the drive selection window if drive is cli-specified 249 | drive="$(yad "${yadflags[@]}" --form --no-escape \ 250 | --field="Name of new drive: " '' \ 251 | --button='Google Drive'!"${DIRECTORY}/icons/gdrive.png":4 \ 252 | --button='OneDrive'!"${DIRECTORY}/icons/onedrive.png":2 \ 253 | --button='Dropbox'!"${DIRECTORY}/icons/dropbox.png":0)" 254 | button=$? 255 | 256 | #If window closed some other way than the drive selection buttons, go back to start page 257 | if [ $button != 4 ] && [ $button != 2 ] && [ $button != 0 ];then 258 | echobright "User did not choose a drive type to create. Going back..." 259 | break 260 | fi 261 | 262 | if [ -z "$drive" ];then 263 | yad "${yadflags[@]}" --text=" A Drive must be given a name. " \ 264 | --button=OK:0 || back 265 | fi 266 | #change button number to script-readable drive type 267 | drivetype="$(echo "$button" | sed s/4/drive/g | sed s/2/onedrive/g | sed s/0/dropbox/g)" 268 | done #past this point, $drive is populated and a valid drive type has been selected. 269 | 270 | if [ $button == 4 ] || [ $button == 2 ] || [ $button == 0 ] || [ ! -z "$drivetype" ];then #this will be skipped if "Going back..." 271 | echobright "To get here from the command line, run this:\n$0 newdrive "\""$drive"\"" "\""$drivetype"\""" 272 | 273 | configfile="$((rclone config file || echo $'\n'"$HOME/.config/rclone/rclone.conf") | tail -n +2)" 274 | mkdir -p "$(dirname "$configfile")" 275 | echocommand "rclone config create "\""$drive"\"" "\""$drivetype"\"" env_auth=false >> "\""$configfile"\""" 276 | 277 | rclone config create "$drive" "$drivetype" env_auth=false >> "$configfile" & 278 | rclonepid=$! 279 | echo "$rclonepid" 280 | 281 | (yad "${yadflags[@]}" --text="A web browser should appear to authorize the connection." --button=' Cancel operation ':0 282 | button=$? 283 | if [ $button != 0 ];then 284 | sleep infinity 285 | fi 286 | ) & 287 | yadpid=$! 288 | 289 | while true;do 290 | #if rclone finishes, then close cancel-button window 291 | if [ ! -e /proc/$rclonepid ];then 292 | kill $yadpid $(list_descendants $yadpid) 293 | break 294 | fi 295 | #if cancel-button window closes, then kill rclone as user must have clicked 'Cancel' 296 | if [ ! -e /proc/$yadpid ];then 297 | kill $rclonepid 298 | break 299 | fi 300 | sleep 1 301 | done 302 | fi #everything in the above if statement is skipped if $button is not 0, 2, or 4 303 | 304 | elif [ "$1" == removedrive ];then 305 | #Usage: main.sh removedrive driveName 306 | echobright "Remove drive" 307 | drive="$(echo "$2" | sed 's/:$//g')" 308 | if [ -z "$(echo "$remotes" | grep "$drive")" ];then 309 | echobright "CLI-selected drive '$drive' does not exist." 310 | drive='' 311 | fi 312 | if [ -z "$drive" ];then 313 | drive="$(choosedrive "Choose a cloud drive to sign out of.")" 314 | fi 315 | [ -z "$drive" ] && back 316 | 317 | echobright "To get here from the command line, run this:\n$0 removedrive "\""$drive"\""" 318 | 319 | echocommand "rclone config disconnect "\""$drive:"\""" 320 | rclone config disconnect "$drive": || echobright "Don't worry - rclone errors above this point are normal and expected for certain cloud storage providers." 321 | 322 | echocommand "rclone config delete "\""$drive"\""" 323 | rclone config delete "$drive" #this rclone option DOES NOT ACCEPT A COLON! 324 | 325 | elif [ "$1" == mountdrive ];then 326 | #Usage: main.sh mountdrive driveName /path/to/mountpoint 327 | echobright "Mount drive" 328 | if [ "$usefuse" == 0 ];then 329 | errorback "Rclone cannot mount a drive when the 'fuse' kernel module is not loaded.\nRun this command to load the kernel module: 'sudo modprobe fuse'" 330 | fi 331 | 332 | drive="$(echo "$2" | sed 's/:$//g')" 333 | if [ -z "$(echo "$remotes" | grep "$drive")" ];then 334 | echobright "CLI-selected drive '$drive' does not exist." 335 | drive='' 336 | fi 337 | if [ -z "$drive" ];then 338 | echo "Choosing drive..." 339 | drive="$(choosedrive)" 340 | fi 341 | [ -z "$drive" ] && back 342 | 343 | directory="$3" #attempt to mount to folder specified by command-line 344 | while true;do 345 | if [ -z "$directory" ];then 346 | echo "Choosing mountpoint..." 347 | directory="$(yad "${yadflags[@]}" --file --directory --mime-filter="Directories | inode/directory" --width=600 --height=400 \ 348 | --text="Choose an empty directory to mount $drive to.")" 349 | [ $? != 0 ] && back 350 | fi 351 | 352 | if [ -z "$directory" ];then 353 | echobright "No directory chosen!" 354 | yad "${yadflags[@]}" --text="No directory chosen!" --button="Try again" 355 | elif [ ! -d "$directory" ];then 356 | echobright "Directory $directory does not exist!" 357 | yad "${yadflags[@]}" --text="No directory chosen!" --button="Try again" 358 | elif [ ! -z "$(ls -A "$directory")" ];then 359 | echobright "Directory $directory contains files!" 360 | yad "${yadflags[@]}" --text=" Directory $directory contains files! "$'\n'" It must be empty. " --button="Choose another folder" 361 | else 362 | break 363 | fi 364 | directory='' 365 | done #past this point, a valid directory is selected that contains no files 366 | 367 | echobright "To get here from the command line, run this:\n$0 mountdrive "\""$drive"\"" "\""$directory"\""" 368 | 369 | #this will run for a minimum of 7 seconds before returning to the main window. 370 | sleep 7 & 371 | sleeppid=$! 372 | (echo "# "; [ "$useshm" == 1 ] && [ "$fastmode" == 0 ] && tail -F /dev/shm/cloudbuddy_current_file || sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title=Mounting \ 373 | --text="Mounting: $drive"$'\n'"To: $directory..." \ 374 | --width=300 --height=100 --center --auto-close --auto-kill \ 375 | --no-buttons 2>/dev/null & 376 | loader_pid=$! 377 | echocommand "rclone mount "\""$drive:"\"" "\""$directory"\""" 378 | setsid bash -c 'errors="$(rclone mount "'"$drive"'": "'"$directory"'" 2>&1)" 379 | [ $? != 0 ] && warning "Rclone failed to mount '"$drive"' to '"$directory"'!'$'\n''Errors: $errors"' & 380 | wait $sleeppid 381 | kill $loader_pid 382 | 383 | elif [ "$1" == unmountdrive ];then 384 | #Usage: main.sh unmountdrive driveName, OR main.sh unmountdrive /path/to/mountpoint 385 | echobright "Unmount drive" 386 | 387 | if [ "$usefuse" == 0 ];then 388 | errorback "Rclone cannot unmount a drive when the 'fuse' kernel module is not loaded.\nRun this command to load the kernel module: 'sudo modprobe fuse'" 389 | fi 390 | 391 | mounts="$(mount | grep fuse.rclone | sed 's/ type fuse.rclone (.*)//g' | sed 's/: on /:/g')" 392 | 393 | drives="$(echo "$mounts" | sed 's/:.*//g')" 394 | mountdirs="$(echo "$mounts" | sed 's/.*://g')" 395 | #echobright "Mounted drives: $drives\nMountdirs: $mountdirs" 396 | 397 | if [ ! -z "$2" ] && echo "$drives" | grep -xq "$(echo "$2" | sed 's/:$//g')" ;then 398 | #cli-specified drive name to unmount 399 | mountpoint="$(echo "$mounts" | grep "$(echo "$2" | sed 's/:$//g'):" | sed 's/.*://g')" 400 | elif [ ! -z "$2" ] && echo "$mountdirs" | grep -q "$2" ;then 401 | #cli-specified directory to unmount 402 | mountpoint="$2" 403 | else 404 | mountpoint="$(echo "$mounts" | sed 's|:| mounted on |g' | sed 's|^||g' | sed 's|$||g' | yad "${yadflags[@]}" --list \ 405 | --width=430 --height=200 \ 406 | --column='' --no-headers --text="Which drive to unmount?")" || back 407 | 408 | #change yad output back to script-readable: only mountpoint remains 409 | mountpoint="$(echo "$mountpoint" | sed 's|||g' | sed 's|||g' | sed 's/.*mounted on //g')" 410 | fi 411 | 412 | #for the terminal-usage announcement, determine what drive is associated with the mountpoint. 413 | drive="$(echo "$mounts" | grep ":$(echo "$mountpoint" | sed 's|/$||g')" | sed 's/:.*//g')" 414 | echobright "To get here from the command line, run this:\n$0 unmountdrive "\""$mountpoint"\""\nOr you could specify the drive name like this:\n$0 unmountdrive "\""$drive"\""" 415 | 416 | echocommand "fusermount -u "\""$mountpoint"\""" 417 | fusermount -u "$mountpoint" || (echocommand "sudo umount "\""$mountpoint"\"""; sudo umount "$mountpoint") || errorback "Failed to unmount $mountpoint!\nErrors: $(fusermount -u "$mountpoint" 2>&1)" 418 | 419 | elif [ "$1" == browsedrive ];then 420 | #Usage: main.sh browsedrive driveName:prefix 421 | echobright "Browse drive" 422 | 423 | drive="$(echo "$2" | sed 's/:.*//g')" 424 | prefix="$(echo "$2" | sed 's/.*://g')" #variable to store subfolder information for the interactive file browser 425 | 426 | if [ -z "$(echo "$remotes" | grep "$drive")" ];then 427 | echo "CLI-selected drive '$drive' does not exist." 428 | drive='' 429 | fi 430 | if [ -z "$drive" ];then 431 | drive="$(choosedrive)" 432 | fi 433 | [ -z "$drive" ] && back 434 | 435 | #CloudBuddy is equipped with two file browsers: a faster one and a slower one. The fast one has fewer bells and whistles, but it's load time is significantly shorter when dealing with hundreds of files. 436 | if [ -z "$fastmode" ];then 437 | fastmode=0 #Set fastmode=0 for more detailed file browser, set fastmode=1 for a simpler, more streamlined file browser 438 | fi 439 | 440 | while true;do 441 | 442 | #ensure prefix has a trailing slash. This GUI does this by design, but there is no guarantee it will be there if cli-specified. 443 | if [ ! -z "$prefix" ];then 444 | prefix="$(echo "$prefix"/ | sed 's|//$|/|g')" 445 | fi 446 | 447 | echobright "To get here from the command line, run this:\n$0 browsedrive "\""$(echo "$drive:$prefix" | sed 's|/$||g' | sed 's/:$//g')"\""" 448 | 449 | [ $useshm == 1 ] && echo '' > /dev/shm/cloudbuddy_current_file 450 | (echo "# "; [ $useshm == 1 ] && [ $fastmode == 0 ] && tail -F /dev/shm/cloudbuddy_current_file || sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title=Scanning \ 451 | --text="Scanning $(echo "$drive:$prefix" | sed 's/:$//g' | sed 's|/$||g')..." \ 452 | --width=300 --height=100 --center --auto-close --auto-kill \ 453 | --no-buttons 2>/dev/null & 454 | loader_pid=$! 455 | 456 | if [ $fastmode == 0 ];then 457 | #get detailed information about each file in current folder. 458 | echocommand "rclone lsjson "\""$drive:$(echo "$prefix" | sed 's|/$||g')"\""" 459 | #Json format converted to parsable raw text. Example line: 460 | #Path=Tripod.JPG,Name=Tripod.JPG,Size=1472358,MimeType=image/jpeg,ModTime=2018-09-02T04:12:36.807Z,IsDir=false,ID=1229VLzjsD1XUm5LgdwlfoWEqwpzbWU9p 461 | filelist="$(rclone lsjson "$drive:$(echo "$prefix" | sed 's|/$||g')" 2>&1 | sed 's/":"/=/g' | sed 's/":/=/g' | sed 's/^{"//g' | sed 's/","/,/g' | sed 's/,"/,/g' | sed 's/"}$//g' | sed 's/},$//g' | sed 's/"},$//g' | sed 's/}$//g' | sed '/^\]$/d' | sed '/^\[$/d')" 462 | [ $? != 0 ] && errorback "rclone failed to acquire a file list from $drive:$prefix!\nErrors: $filelist" 463 | #echo "$filelist" 464 | 465 | #Don't kill the loading window now, kill it when list parsing is done. 466 | #kill $loader_pid #close the progress bar window 467 | 468 | IFS=$'\n' 469 | LIST='' 470 | for file in $filelist ;do 471 | #echo "File info: $file" 472 | 473 | #read in values. Unneeded values are commented out to save time 474 | path="$(echo "$file" | sed 's/^Path=//g' | sed 's/,Name=.*//g')" 475 | name="$(echo "$file" | sed 's/.*,Name=//g' | sed 's/,Size=.*//g')" 476 | #size="$(echo "$file" | sed 's/.*Size=//g' | sed 's/,MimeType=.*//g')" 477 | #mimetype="$(echo "$file" | sed 's/.*MimeType=//g' | sed 's/,ModTime=.*//g')" 478 | #modtime="$(echo "$file" | sed 's/.*ModTime=//g' | sed 's/,IsDir=.*//g')" 479 | #isdir="$(echo "$file" | sed 's/.*IsDir=//g' | sed 's/,ID=.*//g')" 480 | #id="$(echo "$file" | sed 's/.*ID=//g')" 481 | 482 | #to prevent commas in filenames from borking up remainder of parsing, remove Path= and Name= from $file before proceeding 483 | file=",,Size=$(echo "$file" | sed 's/.*,Size=//g')" 484 | 485 | #path="$(echo "$file" | awk -F, '{print $1}')" 486 | #name="$(echo "$file" | awk -F, '{print $2}')" 487 | size="$(echo "$file" | awk -F, '{print $3}')" 488 | mimetype="$(echo "$file" | awk -F, '{print $4}')" 489 | modtime="$(echo "$file" | awk -F, '{print $5}')" 490 | isdir="$(echo "$file" | awk -F, '{print $6}')" 491 | id="$(echo "$file" | awk -F, '{print $7}')" 492 | 493 | #if current file is a directory, add a trailing slash to filename 494 | if [ "$isdir" == 'IsDir=true' ];then 495 | #file="$(echo "$file" | sed 's|,Size=|/,Size=|g')" 496 | [ ! -z "$name" ] && name="${name}/" 497 | icon="${DIRECTORY}/icons/folder.png" 498 | else 499 | icon="${DIRECTORY}/icons/none-24.png" 500 | fi 501 | 502 | echo "# $name" >> /dev/shm/cloudbuddy_current_file 503 | 504 | #LIST="$LIST 505 | #$icon 506 | #$file" 507 | LIST="$LIST 508 | $icon 509 | $path 510 | $name 511 | $size 512 | $mimetype 513 | $modtime 514 | $isdir 515 | $id" 516 | #echo -e "Debug info:\nPath: $path\nName: $name\nSize: $size\nMimeType: $mimetype\nModTime: $modtime\nIsDir: $isdir\nID: $id\n" 517 | done 518 | #ABOVE THIS POINT, $LIST CONTAINS THE "XXXX=" TAKEN FROM THE JSON. 519 | LIST="$(echo "$LIST" | sed 's/.*=//g' | tail -n +2 | sed 's/^-1$/0/g')" 520 | #echo "$LIST" 521 | 522 | #Now that list parsing is done, kill the loading window. 523 | kill $loader_pid #close the progress bar window 524 | 525 | else #fastmode == 1 526 | #simpler file browser mode with less GUI features but it loads faster 527 | echocommand "rclone lsf "\""$drive:$(echo "$prefix" | sed 's|/$||g')"\""" 528 | filelist="$(rclone lsf "$drive:$(echo "$prefix" | sed 's|/$||g')" 2>&1)" 529 | [ $? != 0 ] && errorback "rclone failed to acquire a file list from $drive:$prefix!\nErrors: $filelist" 530 | kill $loader_pid #close the progress bar window 531 | filelist="$(echo "$filelist" | tac | sed 's/$/\n/g' | sed "s|/"'$'"|/\n${DIRECTORY}/icons/folder.png|g" | tac | sed -z "s|\n${DIRECTORY}/icons/folder.png|${DIRECTORY}/icons/folder.png|g" | sed -z "s|\n\n|\n${DIRECTORY}/icons/none-24.png\n|g" | sed -z "s|^\n|${DIRECTORY}/icons/none-24.png\n|g")" 532 | #echo "$filelist" 533 | fi 534 | 535 | #array of buttons to send to file browser window 536 | buttons=() 537 | if [ ! -z "$prefix" ];then 538 | buttons+=(--button=Back!"${DIRECTORY}/icons/back.png"!"Up one level to $(echo "$drive:$(dirname "$prefix" | sed 's/^.$//g')" | sed 's/:$//g')":2) 539 | fi 540 | buttons+=(--button='Add files'!"${DIRECTORY}/icons/upload.png"!"Upload files to the current folder, or into a subfolder if one is selected.":10 \ 541 | --button=Download!"${DIRECTORY}/icons/download.png"!"Download selected item(s) to your local Downloads folder.":8 \ 542 | --button=Move!"${DIRECTORY}/icons/move.png"!"Move the selected file/folder to another location on this drive."$'\n'"If you select multiple items, only the Delete button will be available.":6 \ 543 | --button=Link!"${DIRECTORY}/icons/link.png"!"Generate a link to publicly share the selected file/folder.":4) 544 | 545 | if [ $fastmode == 0 ];then 546 | output="$(echo "$LIST" | sed 's/&/&/g' | yad "${yadflags[@]}" --list --multiple --separator='\n' --title="CloudBuddy - browsing $(echo "$drive:$prefix" | sed 's/:$//g' | sed 's|/$||g')" \ 547 | --column=:IMG --column=Name --column=echoName:HD --column=Size:SZ --column=MimeType:HD --column=ModTime --column=IsDir:HD --column=ID:HD --print-column=3 \ 548 | --width=560 --height=400 --text="$([ ! -z "$prefix" ] && echo -e "Currently in $(echo "$prefix" | sed 's|/$||g').\r")Double-click to open files/folders." \ 549 | "${buttons[@]}" )" 550 | button=$? 551 | else 552 | output="$(echo "$filelist" | sed 's/&/&/g' | yad "${yadflags[@]}" --list --multiple --separator='\n' \ 553 | --column=:IMG --column=Name --print-column=2 \ 554 | --width=300 --height=400 --text="$([ ! -z "$prefix" ] && echo -e "Currently in $(echo "$prefix" | sed 's|/$||g').\r")Double-click to open files/folders." \ 555 | "${buttons[@]}" )" 556 | button=$? 557 | fi 558 | 559 | output="$(echo "$output" | grep .)" 560 | echo "Prefix is $prefix, Output is '$output'" 561 | 562 | if [ $button == 2 ];then 563 | #back button: go up a directory in prefix. 564 | prefix="$(dirname "$prefix" | sed 's/^.$//g')" 565 | elif [ $button == 4 ] && [ ! "$(echo "$output" | wc -l)" -gt 1 ];then 566 | #get link for selected 567 | if [ -z "$output" ];then 568 | warning "In order to generate a link, you must select a file or folder." 569 | else 570 | (echo "# "; sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title="CloudBuddy" \ 571 | --text="Generating link..." \ 572 | --width=300 --height=100 --center --auto-close --auto-kill \ 573 | --no-buttons 2>/dev/null & 574 | loader_pid=$! 575 | echocommand "rclone link "\""$drive:$prefix$output"\""" 576 | link="$(rclone link "$drive:$prefix$output" 2>&1)" 577 | exitcode=$? 578 | kill $loader_pid 579 | if [ $exitcode != 0 ];then 580 | warning "Failed to generate a link for $drive:$prefix$output\nErrors: $link" 581 | else 582 | yad "${yadflags[@]}" --form --columns=2 --width=400 \ 583 | --image="${DIRECTORY}/icons/$(drivetype "$drive").png" \ 584 | --text="Public link below." \ 585 | --field='' "$link" --field=Copy:FBTN "bash -c 'echo "\""$link"\"" | xclip -sel clip'" \ 586 | --button=Close:0 587 | fi 588 | fi 589 | elif [ $button == 6 ];then 590 | #move selected 591 | if [ -z "$output" ];then 592 | warning "A file or folder must be selected in order to move it!" 593 | else 594 | if [ "$(echo "$output" | wc -l)" -gt 1 ];then 595 | #multiple items selected, only display Delete option. 596 | moveto="$(echo -en "$output" | yad "${yadflags[@]}" --text-info --fontname=12 --wrap \ 597 | --width=400 --text="When multiple items are selected, only the Delete option is available."$'\n'"These items were selected:" \ 598 | --button=Cancel:1 \ 599 | --button=Delete!"${DIRECTORY}/icons/trash.png"!" Delete the selected $(echo "$output" | wc -l) items from the cloud."$'\n'" Note: Most cloud storage providers will save deleted items in a recovery folder. ":2)" 600 | button=$? 601 | else 602 | #single file/folder selected, display the usual 'Move' options. 603 | moveto="$(yad "${yadflags[@]}" --form --width=400 --text=" Enter a path to move the chosen file:" \ 604 | --field="From ":RO "$prefix$(echo "$output" | sed 's|/$||g')" \ 605 | --field="To " "$prefix$(echo "$output" | sed 's|/$||g')" \ 606 | --button=Cancel:1 \ 607 | --button=Delete!"${DIRECTORY}/icons/trash.png"!"Delete $drive:$prefix$(echo "$output" | sed 's|/$||g') from the cloud."$'\n'"Note: Most cloud storage providers will save deleted items in a recovery folder.":2 \ 608 | --button=Move!"${DIRECTORY}/icons/move.png":0)" 609 | button=$? 610 | fi 611 | 612 | if [ $button == 0 ];then 613 | #move file 614 | 615 | #remove trailing and leading slashes from output, and only keep second line 616 | moveto="$(echo "$moveto" | grep . | sed 's|^/||g' | sed 's|/$||g' | sed -n 2p)" 617 | 618 | (echo "# "; sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title="Moving..." \ 619 | --text="Moving $prefix$output"$'\n'"to $moveto..." \ 620 | --width=300 --height=100 --center --auto-close --auto-kill \ 621 | --no-buttons 2>/dev/null & 622 | loader_pid=$! 623 | echocommand "rclone moveto "\""$drive:$prefix$output"\"" "\""$drive:$moveto"\""" 624 | errors="$(rclone moveto "$drive:$prefix$output" "$drive:$moveto" 2>&1)" 625 | if [ $? != 0 ];then 626 | warning "Failed to move $prefix$output to $moveto.\nErrors: $errors" 627 | fi 628 | kill $loader_pid 629 | 630 | elif [ $button == 2 ];then 631 | #delete file 632 | 633 | #delete every file found in $output, one at a time 634 | IFS=$'\n' 635 | for file in $output ;do 636 | (echo "# "; sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title="Deleting..." \ 637 | --text="Deleting $drive:$prefix$file..." \ 638 | --width=300 --height=100 --center --auto-close --auto-kill \ 639 | --no-buttons 2>/dev/null & 640 | loader_pid=$! 641 | if [[ "$file" == */ ]];then 642 | #if folder, delete folder 643 | echocommand "rclone purge "\""$drive:$prefix$file"\""" 644 | set -o pipefail 645 | errors="$(rclone purge "$drive:$prefix$file" 2>&1 | tee /dev/stderr)" 646 | exitcode=$? 647 | else 648 | echocommand "rclone deletefile "\""$drive:$prefix$file"\""" 649 | set -o pipefail 650 | errors="$(rclone deletefile "$drive:$prefix$file" 2>&1 | tee /dev/stderr)" 651 | exitcode=$? 652 | fi 653 | [ $exitcode != 0 ] && warning "Rclone failed to delete $drive:$prefix$file. Errors:\n$errors" & 654 | kill $loader_pid #close the progress bar window 655 | done 656 | fi 657 | fi 658 | elif [ $button == 8 ];then 659 | #download selected item(s) 660 | echo Download 661 | if [ -z "$output" ];then 662 | warning "At least one file or folder must be selected in order the Download button to do anything!" 663 | else 664 | destinationdir="$HOME/Downloads" 665 | 666 | "${DIRECTORY}/terminal-run" " 667 | trap 'sleep infinity' EXIT 668 | output="\""$output"\"" 669 | source "\""$0"\"" source 670 | IFS=$'\n' 671 | failed=0 672 | for file in "\$"output ;do 673 | echoprogress "\""Downloading "\"""\\""\"""\""$drive:$prefix"\$"file"\"""\\""\"""\"""\"" 674 | if [[ "\"""\$"file"\"" == */ ]];then 675 | #if downloading directory, create subdirectory on destination to preserve structure 676 | echocommand "\""rclone copy -P --stats-one-line "\"""\\""\"""\""$drive:$prefix"\$"file"\"""\\""\"""\"" "\"""\\""\"""\""$destinationdir/"\$"file"\"""\\""\"""\"""\"" 677 | #re-run on failure if it took over 10 minutes 678 | while true;do 679 | start_time="\$"(date +%s) 680 | rclone copy -P --stats-one-line "\""$drive:$prefix"\$"file"\"" "\""$destinationdir/"\$"file"\"" 681 | exitcode="\$"? 682 | end_time="\$"(date +%s) 683 | if [ "\$"exitcode == 0 ];then 684 | break 685 | elif [ "\$"exitcode != 0 ] && [ "\$"((end_time-start_time)) -ge 600 ];then 686 | echo -e '\n\e[101m\e[30mDownload failed! But it was going for 10 minutes, so trying again.\e[39m\e[49m' 687 | else 688 | break #give up with exitcode=1 689 | fi 690 | done 691 | else 692 | echocommand "\""rclone copy -P --stats-one-line "\"""\\""\"""\""$drive:$prefix"\$"file"\"""\\""\"""\"" "\"""\\""\"""\""$destinationdir"\"""\\""\"""\"""\"" 693 | rclone copy -P --stats-one-line "\""$drive:$prefix"\$"file"\"" "\""$destinationdir"\"" 694 | exitcode="\$"? 695 | fi 696 | if [ "\$"exitcode != 0 ];then 697 | failed=1 698 | fi 699 | done 700 | if [ "\$"failed == 0 ];then 701 | echo -e '\e[102m\e[30mDownload succeeded! Close this window to exit.\e[39m\e[49m' 702 | else 703 | echo -e '\n\e[101m\e[30mDownload failed! Errors above.\e[39m\e[49m' 704 | fi 705 | " "Downloading $(echo "$output" | wc -l) file$([ $(echo "$output" | wc -l) != 1 ] && echo s) to $(echo "$destinationdir" | sed "s|$HOME/|~/|g")" 706 | fi 707 | elif [ $button == 10 ] && [ ! "$(echo "$output" | wc -l)" -gt 1 ];then 708 | #upload files 709 | if [ ! -z "$output" ] && [[ "$output" == */ ]];then 710 | #if folder selected, destination is prefix/output 711 | destinationdir="$prefix$output" 712 | else 713 | destinationdir="$prefix" 714 | fi 715 | destinationdir="$(echo "$destinationdir" | sed 's|/$||g')" 716 | 717 | output="$(yad "${yadflags[@]}" --dnd --on-top --text=" Drag-n-drop files here to upload them to $(echo "$drive:$destinationdir" | sed 's/:$//g'). " \ 718 | --button=Cancel!!"Go back":1 --button=Upload!"${DIRECTORY}/icons/upload.png"!"Upload the files you have dropped onto this window":0)" 719 | button=$? 720 | #convert output from "file://" format to absolute paths format 721 | output="$(echo "$output" | sed 's|^file://||')" 722 | 723 | if [ $button == 0 ] && [ ! -z "$output" ];then 724 | #upload each file at a time with rclone 725 | "${DIRECTORY}/terminal-run" " 726 | trap 'sleep infinity' EXIT 727 | output="\""$output"\"" 728 | source "\""$0"\"" source 729 | IFS=$'\n' 730 | failed=0 731 | for file in "\$"output ;do 732 | echoprogress "\""Copying "\$"file..."\"" 733 | 734 | if [ -d "\"""\$"file"\"" ];then 735 | #if uploading directory, create subdirectory on destination to preserve structure 736 | echocommand rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir/"\$"(basename "\"""\$"file"\"")"\"" 737 | rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir/"\$"(basename "\"""\$"file"\"")"\"" 738 | exitcode="\$"? 739 | else 740 | echocommand rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir"\"" 741 | rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir"\"" 742 | exitcode="\$"? 743 | fi 744 | if [ "\$"exitcode != 0 ];then 745 | failed=1 746 | fi 747 | done 748 | if [ "\$"failed == 0 ];then 749 | echo -e '\e[102m\e[30mUpload succeeded! Close this window to exit.\e[39m\e[49m' 750 | else 751 | echo -e '\e[101m\e[30mUpload failed! Errors above.\e[39m\e[49m' 752 | fi 753 | #create yad button-box to refresh main window 754 | yad "\\"-"\\"-title=Refresh? --text="\""Upload complete. Refresh the file list?"\"" --width=300 --height=100 --center --on-top --focus --window-icon="\""${DIRECTORY}/icons/cloud-square.png"\"" --button=Refresh:0 && "\""$0"\"" browsedrive "\""$drive:$prefix"\"" 755 | " "Uploading $(echo "$output" | wc -l) file$([ $(echo "$output" | wc -l) != 1 ] && echo s) to $(echo "$drive:$destinationdir" | sed 's/:$//g')" 756 | elif [ $button == 0 ] && [ -z "$output" ];then 757 | warning "No files were dropped onto the Upload window.\nAs a result, no files will be uploaded to $(echo "$drive:$destinationdir" | sed 's/:$//g')." 758 | fi 759 | 760 | elif [ $button == 0 ] && [ ! "$(echo "$output" | wc -l)" -gt 1 ];then 761 | #open file/folder - double-clicked 762 | if [[ "$output" == */ ]];then 763 | echobright "Folder selected: $(echo "$output" | sed 's|/$||g')" 764 | if [ ! -z $prefix ];then 765 | prefix="$prefix$output" 766 | else 767 | prefix="$output" 768 | fi 769 | else 770 | echobright "File selected: $(echo "$output")" 771 | 772 | #download file 773 | tmpdir="$(mktemp -d)" 774 | 775 | set -o pipefail 776 | (echo "# "; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title=Downloading \ 777 | --text="Downloading:"$'\n'"$drive:$prefix$output"$'\n'"To: $tmpdir..." \ 778 | --width=300 --height=100 --center --auto-close --auto-kill \ 779 | --button=Cancel:"bash -c '$0 browsedrive "\""$drive"\$"(echo Ogo= | base64 -d)$prefix"\""; kill $$ $(list_descendants $$)'" & 780 | loader_pid=$! 781 | 782 | echocommand "rclone copy -P --stats-one-line "\""$drive:$prefix$output\""" "\""$tmpdir"\""" 783 | set -o pipefail 784 | errors="$(rclone copy -P --stats-one-line "$drive:$prefix$output" "$tmpdir" 2>&1 | tee /dev/stderr)" 785 | exitcode=$? 786 | kill $loader_pid #close the progress bar window 787 | 788 | if [ $button == 1 ];then 789 | true #cancel button clicked 790 | elif [ ! -f "$tmpdir/$output" ];then 791 | #check for output file existence 792 | warning "Rclone succeeded to download $output, but $tmpdir/$output does not exist.\nErrors: $errors" 793 | else 794 | #open file 795 | xdg-open "$tmpdir/$output" 796 | fi 797 | fi 798 | elif [ "$(echo "$output" | wc -l)" -gt 1 ] && ([ $button == 0 ] || [ $button == 4 ] || [ $button == 10 ]);then 799 | #multiple items selected 800 | warning "This button cannot work when multiple items are selected!" 801 | else #unknown button 802 | break 803 | fi 804 | done #end of interactive file browser - forever loop 805 | 806 | elif [ "$1" == webinterface ];then 807 | echobright "Web Interface" 808 | echobright "To get here from the command line, run this:\n$0 webinterface" 809 | "${DIRECTORY}/terminal-run" "trap 'sleep infinity' EXIT 810 | source "\""$0"\"" source 811 | echocommand 'rclone rcd --rc-web-gui' 812 | rclone rcd --rc-web-gui 813 | " "Running Rclone Web Interface" 814 | fi #END OF COMMAND-LINE subscripts 815 | 816 | #Run main window as a child process if command-line flags were used above for a subscript. This prevents subsequent subscripts from reading undesired cli values. 817 | if [ ! -z "$1" ];then 818 | back 819 | fi 820 | 821 | #if rclone drive mounted anywhere, display button to unmount drives 822 | if [ ! -z "$(mount | grep fuse.rclone)" ] && [ "$usefuse" == 1 ];then 823 | unmountbutton=(--field="Unmount Drive"!"${DIRECTORY}/icons/eject.png"!"Unmount a cloud drive from your filesystem":FBTN "$0 unmountdrive") 824 | fi 825 | 826 | #if user has configured at least 1 drive, display these buttons: 827 | if [ ! -z "$remotes" ];then 828 | if [ "$usefuse" == 1 ];then 829 | mountbutton=(--field="Mount Drive"!"${DIRECTORY}/icons/mount.png"!"Connect a cloud drive to your computer like a USB drive":FBTN "$0 mountdrive") 830 | fi 831 | browsebutton=(--field="Browse Drive"!"${DIRECTORY}/icons/browse.png"!"A built-in file browser to create sharable links, upload files, download files, move files, or delete files.":FBTN "$0 browsedrive") 832 | removebutton=(--field="Remove Drive"!"${DIRECTORY}/icons/remove.png"!"Sign out of an existing cloud drive":FBTN "$0 removedrive") 833 | fi 834 | 835 | yad "${yadflags[@]}" --width=400 --form --columns=2 --no-buttons \ 836 | --field="New Drive"!"${DIRECTORY}/icons/new.png"!"Connect to a new cloud drive":FBTN "$0 newdrive" \ 837 | "${mountbutton[@]}" \ 838 | "${browsebutton[@]}" \ 839 | "${removebutton[@]}" \ 840 | "${unmountbutton[@]}" \ 841 | --field="Web Interface"!"${DIRECTORY}/icons/webinterface.png"!"Launch rclone's built-in browser interface.":FBTN "$0 webinterface" \ 842 | 843 | -------------------------------------------------------------------------------- /terminal-run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #$1 is the command to be run. 4 | #$2 is the title. 5 | 6 | #set to 1 to display which terminal is being used 7 | DEBUG=0 8 | 9 | if [ -f /usr/bin/lxterminal ];then 10 | lxterminal --title="$2" -e bash -c "$1" 11 | [ $DEBUG == 1 ] && echo lxterminal 12 | 13 | elif [ -f /usr/bin/xfce4-terminal ];then 14 | xfce4-terminal --title="$2" -x bash -c "$1" 15 | [ $DEBUG == 1 ] && echo xfce4-terminal 16 | 17 | elif [ -f /usr/bin/mate-terminal ];then 18 | #mate-terminal --title="$2" -e "bash -c "\""$1"\""" 19 | mate-terminal --title="$2" -x bash -c "$1" 20 | [ $DEBUG == 1 ] && echo mate-terminal 21 | 22 | elif [ -f /usr/bin/xterm ];then 23 | xterm -T "$2" -e bash -c "$1" 24 | [ $DEBUG == 1 ] && echo xterm 25 | 26 | elif [ -f /usr/bin/konsole ];then 27 | konsole -p tabtitle="$2" -e bash <(echo "$1") 28 | [ $DEBUG == 1 ] && echo konsole 29 | 30 | elif [ -f /usr/bin/terminator ];then 31 | terminator -T "$2" -x bash -c "$1" 32 | [ $DEBUG == 1 ] && echo terminator 33 | 34 | elif [ -f /usr/bin/gnome-terminal ];then 35 | gnome-terminal --title "$2" -x bash -c "$1" 36 | [ $DEBUG == 1 ] && echo gnome-terminal 37 | 38 | elif [ -f /usr/bin/qterminal ];then 39 | qterminal -e bash <(echo "$1") 40 | [ $DEBUG == 1 ] && echo qterminal 41 | 42 | elif [ -f /usr/bin/x-terminal-emulator ];then 43 | $(readlink -f /usr/bin/x-terminal-emulator) -e bash -c "$1" 44 | [ $DEBUG == 1 ] && echo x-terminal-emulator 45 | 46 | else 47 | echo "Failed to locate any terminal emulators!!!" 48 | exit 1 49 | fi --------------------------------------------------------------------------------