├── 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/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/back.png -------------------------------------------------------------------------------- /icons/browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/browse.png -------------------------------------------------------------------------------- /icons/cloud-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/cloud-square.png -------------------------------------------------------------------------------- /icons/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/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/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/download.png -------------------------------------------------------------------------------- /icons/dropbox-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/dropbox-24.png -------------------------------------------------------------------------------- /icons/dropbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/dropbox.png -------------------------------------------------------------------------------- /icons/eject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/eject.png -------------------------------------------------------------------------------- /icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/folder.png -------------------------------------------------------------------------------- /icons/gdrive-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/gdrive-24.png -------------------------------------------------------------------------------- /icons/gdrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/gdrive.png -------------------------------------------------------------------------------- /icons/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/link.png -------------------------------------------------------------------------------- /icons/mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/mount.png -------------------------------------------------------------------------------- /icons/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/move.png -------------------------------------------------------------------------------- /icons/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/new.png -------------------------------------------------------------------------------- /icons/none-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/none-24.png -------------------------------------------------------------------------------- /icons/onedrive-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/onedrive-24.png -------------------------------------------------------------------------------- /icons/onedrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/onedrive.png -------------------------------------------------------------------------------- /icons/other-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/other-24.png -------------------------------------------------------------------------------- /icons/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/remove.png -------------------------------------------------------------------------------- /icons/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/stop.png -------------------------------------------------------------------------------- /icons/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/trash.png -------------------------------------------------------------------------------- /icons/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/icons/upload.png -------------------------------------------------------------------------------- /icons/webinterface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Botspot/cloudbuddy/4bbc160f4efbafb6b39f95bb54c79ff9731e25e5/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 | archname=arm 161 | if [ "$arch" == 64 ];then 162 | archname=arm64 163 | fi 164 | echocommand "wget https://downloads.rclone.org/rclone-current-linux-$archname.zip -O rclone-current-linux-$archname.zip" 165 | 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!" 166 | 167 | echocommand "unzip -j -o -d rclone-temp rclone-current-linux-$archname.zip" 168 | unzip -j -o -d rclone-temp rclone-current-linux-$archname.zip || error "Failed to extract rclone-current-linux-$archname.zip" 169 | 170 | echocommand "sudo mv rclone-temp/rclone /usr/bin/rclone" 171 | sudo mv -f rclone-temp/rclone /usr/bin/rclone || error "Failed to move rclone binary to /usr/bin/rclone" 172 | echobright "sudo mv rclone-temp/rclone.1 /usr/share/man/man1/rclone.1" 173 | sudo mv -f rclone-temp/rclone.1 /usr/share/man/man1/rclone.1 174 | 175 | echocommand "sudo chown root: /usr/bin/rclone" 176 | sudo chown root: /usr/bin/rclone 177 | echocommand "rm -rf rclone-current-linux-$archname.zip rclone-temp" 178 | rm -rf rclone-current-linux-$archname.zip rclone-temp 179 | echobright "Rclone installation successful. Proceeding..." 180 | fi 181 | 182 | #check for fuse kernel module. Launch it if necessary. If still not running, set usefuse var to zero. 183 | usefuse=1 184 | if ! ls /sys/module | awk '{print $1}' | grep -qFx fuse ;then 185 | echobright "Kernel module 'fuse' not found. Attempting to load it..." 186 | echocommand "sudo modprobe fuse" 187 | sudo modprobe fuse 188 | 189 | #check again to ensure fuse module running 190 | if ! ls /sys/module | awk '{print $1}' | grep -qFx fuse ;then 191 | usefuse=0 192 | warning "Failed to load kernel module 'fuse'!\nAs a result, rclone cannot mount cloud drives." 193 | fi 194 | fi 195 | 196 | #menu button 197 | if [ ! -f ~/.local/share/applications/cloudbuddy.desktop ];then 198 | echobright "Creating menu button..." 199 | mkdir -p ~/.local/share/applications 200 | echo "[Desktop Entry] 201 | Name=CloudBuddy 202 | Comment=The ultimate cloud storage manager. 203 | Exec=$0 204 | Icon=${DIRECTORY}/icons/cloud-square.png 205 | Terminal=false 206 | Type=Application 207 | Categories=Application;Network;RemoteAccess; 208 | StartupNotify=true" > ~/.local/share/applications/cloudbuddy.desktop 209 | fi 210 | 211 | #icon 212 | if [ ! -f ~/.local/share/icons/cloudbuddy.png ];then 213 | echobright "copying program icon..." 214 | mkdir -p ~/.local/share/icons 215 | cp ${DIRECTORY}/icons/cloud-square.png ~/.local/share/icons/cloudbuddy.png 216 | fi 217 | 218 | if [ "$1" == setup ];then 219 | echo "CloudBuddy setup complete." 220 | exit 0 221 | fi 222 | 223 | if [ -d /dev/shm ];then 224 | #if linux kernel's RAM disk directory exists, use it for signaling 225 | useshm=1 226 | else 227 | useshm=0 228 | fi 229 | 230 | yadflags=(--center --title="CloudBuddy" --separator='\n' --window-icon="${DIRECTORY}/icons/cloud-square.png" --class CloudBuddy --name CloudBuddy) 231 | 232 | #generate list of remotes that rclone currently knows about 233 | remotes="$(rclone listremotes)" 234 | 235 | #COMMAND-LINE SUBSCRIPTS 236 | if [ "$1" == newdrive ];then 237 | #Usage: main.sh newdrive driveName drivetype 238 | echobright "New drive" 239 | drive="$3" 240 | drivetype="$2" 241 | 242 | while [ -z "$drive" ];do #skip the drive selection window if drive is cli-specified 243 | drive="$(yad "${yadflags[@]}" --form --no-escape \ 244 | --field="Name of new drive: " '' \ 245 | --button='Google Drive'!"${DIRECTORY}/icons/gdrive.png":4 \ 246 | --button='OneDrive'!"${DIRECTORY}/icons/onedrive.png":2 \ 247 | --button='Dropbox'!"${DIRECTORY}/icons/dropbox.png":0)" 248 | button=$? 249 | 250 | #If window closed some other way than the drive selection buttons, go back to start page 251 | if [ $button != 4 ] && [ $button != 2 ] && [ $button != 0 ];then 252 | echobright "User did not choose a drive type to create. Going back..." 253 | break 254 | fi 255 | 256 | if [ -z "$drive" ];then 257 | yad "${yadflags[@]}" --text=" A Drive must be given a name. " \ 258 | --button=OK:0 || back 259 | fi 260 | #change button number to script-readable drive type 261 | drivetype="$(echo "$button" | sed s/4/drive/g | sed s/2/onedrive/g | sed s/0/dropbox/g)" 262 | done #past this point, $drive is populated and a valid drive type has been selected. 263 | 264 | if [ $button == 4 ] || [ $button == 2 ] || [ $button == 0 ] || [ ! -z "$drivetype" ];then #this will be skipped if "Going back..." 265 | echobright "To get here from the command line, run this:\n$0 newdrive "\""$drive"\"" "\""$drivetype"\""" 266 | 267 | configfile="$((rclone config file || echo $'\n'"$HOME/.config/rclone/rclone.conf") | tail -n +2)" 268 | mkdir -p "$(dirname "$configfile")" 269 | echocommand "rclone config create "\""$drive"\"" "\""$drivetype"\"" env_auth=false >> "\""$configfile"\""" 270 | 271 | rclone config create "$drive" "$drivetype" env_auth=false >> "$configfile" & 272 | rclonepid=$! 273 | echo "$rclonepid" 274 | 275 | (yad "${yadflags[@]}" --text="A web browser should appear to authorize the connection." --button=' Cancel operation ':0 276 | button=$? 277 | if [ $button != 0 ];then 278 | sleep infinity 279 | fi 280 | ) & 281 | yadpid=$! 282 | 283 | while true;do 284 | #if rclone finishes, then close cancel-button window 285 | if [ ! -e /proc/$rclonepid ];then 286 | kill $yadpid $(list_descendants $yadpid) 287 | break 288 | fi 289 | #if cancel-button window closes, then kill rclone as user must have clicked 'Cancel' 290 | if [ ! -e /proc/$yadpid ];then 291 | kill $rclonepid 292 | break 293 | fi 294 | sleep 1 295 | done 296 | fi #everything in the above if statement is skipped if $button is not 0, 2, or 4 297 | 298 | elif [ "$1" == removedrive ];then 299 | #Usage: main.sh removedrive driveName 300 | echobright "Remove drive" 301 | drive="$(echo "$2" | sed 's/:$//g')" 302 | if [ -z "$(echo "$remotes" | grep "$drive")" ];then 303 | echobright "CLI-selected drive '$drive' does not exist." 304 | drive='' 305 | fi 306 | if [ -z "$drive" ];then 307 | drive="$(choosedrive "Choose a cloud drive to sign out of.")" 308 | fi 309 | [ -z "$drive" ] && back 310 | 311 | echobright "To get here from the command line, run this:\n$0 removedrive "\""$drive"\""" 312 | 313 | echocommand "rclone config disconnect "\""$drive:"\""" 314 | rclone config disconnect "$drive": || echobright "Don't worry - rclone errors above this point are normal and expected for certain cloud storage providers." 315 | 316 | echocommand "rclone config delete "\""$drive"\""" 317 | rclone config delete "$drive" #this rclone option DOES NOT ACCEPT A COLON! 318 | 319 | elif [ "$1" == mountdrive ];then 320 | #Usage: main.sh mountdrive driveName /path/to/mountpoint 321 | echobright "Mount drive" 322 | if [ "$usefuse" == 0 ];then 323 | 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'" 324 | fi 325 | 326 | drive="$(echo "$2" | sed 's/:$//g')" 327 | if [ -z "$(echo "$remotes" | grep "$drive")" ];then 328 | echobright "CLI-selected drive '$drive' does not exist." 329 | drive='' 330 | fi 331 | if [ -z "$drive" ];then 332 | echo "Choosing drive..." 333 | drive="$(choosedrive)" 334 | fi 335 | [ -z "$drive" ] && back 336 | 337 | directory="$3" #attempt to mount to folder specified by command-line 338 | while true;do 339 | if [ -z "$directory" ];then 340 | echo "Choosing mountpoint..." 341 | directory="$(yad "${yadflags[@]}" --file --directory --mime-filter="Directories | inode/directory" --width=600 --height=400 \ 342 | --text="Choose an empty directory to mount $drive to.")" 343 | [ $? != 0 ] && back 344 | fi 345 | 346 | if [ -z "$directory" ];then 347 | echobright "No directory chosen!" 348 | yad "${yadflags[@]}" --text="No directory chosen!" --button="Try again" 349 | elif [ ! -d "$directory" ];then 350 | echobright "Directory $directory does not exist!" 351 | yad "${yadflags[@]}" --text="No directory chosen!" --button="Try again" 352 | elif [ ! -z "$(ls -A "$directory")" ];then 353 | echobright "Directory $directory contains files!" 354 | yad "${yadflags[@]}" --text=" Directory $directory contains files! "$'\n'" It must be empty. " --button="Choose another folder" 355 | else 356 | break 357 | fi 358 | directory='' 359 | done #past this point, a valid directory is selected that contains no files 360 | 361 | echobright "To get here from the command line, run this:\n$0 mountdrive "\""$drive"\"" "\""$directory"\""" 362 | 363 | #this will run for a minimum of 7 seconds before returning to the main window. 364 | sleep 7 & 365 | sleeppid=$! 366 | (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 \ 367 | --text="Mounting: $drive"$'\n'"To: $directory..." \ 368 | --width=300 --height=100 --center --auto-close --auto-kill \ 369 | --no-buttons 2>/dev/null & 370 | loader_pid=$! 371 | echocommand "rclone mount "\""$drive:"\"" "\""$directory"\""" 372 | setsid bash -c 'errors="$(rclone mount "'"$drive"'": "'"$directory"'" 2>&1)" 373 | [ $? != 0 ] && warning "Rclone failed to mount '"$drive"' to '"$directory"'!'$'\n''Errors: $errors"' & 374 | wait $sleeppid 375 | kill $loader_pid 376 | 377 | elif [ "$1" == unmountdrive ];then 378 | #Usage: main.sh unmountdrive driveName, OR main.sh unmountdrive /path/to/mountpoint 379 | echobright "Unmount drive" 380 | 381 | if [ "$usefuse" == 0 ];then 382 | 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'" 383 | fi 384 | 385 | mounts="$(mount | grep fuse.rclone | sed 's/ type fuse.rclone (.*)//g' | sed 's/: on /:/g')" 386 | 387 | drives="$(echo "$mounts" | sed 's/:.*//g')" 388 | mountdirs="$(echo "$mounts" | sed 's/.*://g')" 389 | #echobright "Mounted drives: $drives\nMountdirs: $mountdirs" 390 | 391 | if [ ! -z "$2" ] && echo "$drives" | grep -xq "$(echo "$2" | sed 's/:$//g')" ;then 392 | #cli-specified drive name to unmount 393 | mountpoint="$(echo "$mounts" | grep "$(echo "$2" | sed 's/:$//g'):" | sed 's/.*://g')" 394 | elif [ ! -z "$2" ] && echo "$mountdirs" | grep -q "$2" ;then 395 | #cli-specified directory to unmount 396 | mountpoint="$2" 397 | else 398 | mountpoint="$(echo "$mounts" | sed 's|:| mounted on |g' | sed 's|^||g' | sed 's|$||g' | yad "${yadflags[@]}" --list \ 399 | --width=430 --height=200 \ 400 | --column='' --no-headers --text="Which drive to unmount?")" || back 401 | 402 | #change yad output back to script-readable: only mountpoint remains 403 | mountpoint="$(echo "$mountpoint" | sed 's|||g' | sed 's|||g' | sed 's/.*mounted on //g')" 404 | fi 405 | 406 | #for the terminal-usage announcement, determine what drive is associated with the mountpoint. 407 | drive="$(echo "$mounts" | grep ":$(echo "$mountpoint" | sed 's|/$||g')" | sed 's/:.*//g')" 408 | 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"\""" 409 | 410 | echocommand "fusermount -u "\""$mountpoint"\""" 411 | fusermount -u "$mountpoint" || (echocommand "sudo umount "\""$mountpoint"\"""; sudo umount "$mountpoint") || errorback "Failed to unmount $mountpoint!\nErrors: $(fusermount -u "$mountpoint" 2>&1)" 412 | 413 | elif [ "$1" == browsedrive ];then 414 | #Usage: main.sh browsedrive driveName:prefix 415 | echobright "Browse drive" 416 | 417 | drive="$(echo "$2" | sed 's/:.*//g')" 418 | prefix="$(echo "$2" | sed 's/.*://g')" #variable to store subfolder information for the interactive file browser 419 | 420 | if [ -z "$(echo "$remotes" | grep "$drive")" ];then 421 | echo "CLI-selected drive '$drive' does not exist." 422 | drive='' 423 | fi 424 | if [ -z "$drive" ];then 425 | drive="$(choosedrive)" 426 | fi 427 | [ -z "$drive" ] && back 428 | 429 | #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. 430 | if [ -z "$fastmode" ];then 431 | fastmode=0 #Set fastmode=0 for more detailed file browser, set fastmode=1 for a simpler, more streamlined file browser 432 | fi 433 | 434 | while true;do 435 | 436 | #ensure prefix has a trailing slash. This GUI does this by design, but there is no guarantee it will be there if cli-specified. 437 | if [ ! -z "$prefix" ];then 438 | prefix="$(echo "$prefix"/ | sed 's|//$|/|g')" 439 | fi 440 | 441 | echobright "To get here from the command line, run this:\n$0 browsedrive "\""$(echo "$drive:$prefix" | sed 's|/$||g' | sed 's/:$//g')"\""" 442 | 443 | [ $useshm == 1 ] && echo '' > /dev/shm/cloudbuddy_current_file 444 | (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 \ 445 | --text="Scanning $(echo "$drive:$prefix" | sed 's/:$//g' | sed 's|/$||g')..." \ 446 | --width=300 --height=100 --center --auto-close --auto-kill \ 447 | --no-buttons 2>/dev/null & 448 | loader_pid=$! 449 | 450 | if [ $fastmode == 0 ];then 451 | #get detailed information about each file in current folder. 452 | echocommand "rclone lsjson "\""$drive:$(echo "$prefix" | sed 's|/$||g')"\""" 453 | #Json format converted to parsable raw text. Example line: 454 | #Path=Tripod.JPG,Name=Tripod.JPG,Size=1472358,MimeType=image/jpeg,ModTime=2018-09-02T04:12:36.807Z,IsDir=false,ID=1229VLzjsD1XUm5LgdwlfoWEqwpzbWU9p 455 | 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')" 456 | [ $? != 0 ] && errorback "rclone failed to acquire a file list from $drive:$prefix!\nErrors: $filelist" 457 | #echo "$filelist" 458 | 459 | #Don't kill the loading window now, kill it when list parsing is done. 460 | #kill $loader_pid #close the progress bar window 461 | 462 | IFS=$'\n' 463 | LIST='' 464 | for file in $filelist ;do 465 | #echo "File info: $file" 466 | 467 | #read in values. Unneeded values are commented out to save time 468 | path="$(echo "$file" | sed 's/^Path=//g' | sed 's/,Name=.*//g')" 469 | name="$(echo "$file" | sed 's/.*,Name=//g' | sed 's/,Size=.*//g')" 470 | #size="$(echo "$file" | sed 's/.*Size=//g' | sed 's/,MimeType=.*//g')" 471 | #mimetype="$(echo "$file" | sed 's/.*MimeType=//g' | sed 's/,ModTime=.*//g')" 472 | #modtime="$(echo "$file" | sed 's/.*ModTime=//g' | sed 's/,IsDir=.*//g')" 473 | #isdir="$(echo "$file" | sed 's/.*IsDir=//g' | sed 's/,ID=.*//g')" 474 | #id="$(echo "$file" | sed 's/.*ID=//g')" 475 | 476 | #to prevent commas in filenames from borking up remainder of parsing, remove Path= and Name= from $file before proceeding 477 | file=",,Size=$(echo "$file" | sed 's/.*,Size=//g')" 478 | 479 | #path="$(echo "$file" | awk -F, '{print $1}')" 480 | #name="$(echo "$file" | awk -F, '{print $2}')" 481 | size="$(echo "$file" | awk -F, '{print $3}')" 482 | mimetype="$(echo "$file" | awk -F, '{print $4}')" 483 | modtime="$(echo "$file" | awk -F, '{print $5}')" 484 | isdir="$(echo "$file" | awk -F, '{print $6}')" 485 | id="$(echo "$file" | awk -F, '{print $7}')" 486 | 487 | #if current file is a directory, add a trailing slash to filename 488 | if [ "$isdir" == 'IsDir=true' ];then 489 | #file="$(echo "$file" | sed 's|,Size=|/,Size=|g')" 490 | [ ! -z "$name" ] && name="${name}/" 491 | icon="${DIRECTORY}/icons/folder.png" 492 | else 493 | icon="${DIRECTORY}/icons/none-24.png" 494 | fi 495 | 496 | echo "# $name" >> /dev/shm/cloudbuddy_current_file 497 | 498 | #LIST="$LIST 499 | #$icon 500 | #$file" 501 | LIST="$LIST 502 | $icon 503 | $path 504 | $name 505 | $size 506 | $mimetype 507 | $modtime 508 | $isdir 509 | $id" 510 | #echo -e "Debug info:\nPath: $path\nName: $name\nSize: $size\nMimeType: $mimetype\nModTime: $modtime\nIsDir: $isdir\nID: $id\n" 511 | done 512 | #ABOVE THIS POINT, $LIST CONTAINS THE "XXXX=" TAKEN FROM THE JSON. 513 | LIST="$(echo "$LIST" | sed 's/.*=//g' | tail -n +2 | sed 's/^-1$/0/g')" 514 | #echo "$LIST" 515 | 516 | #Now that list parsing is done, kill the loading window. 517 | kill $loader_pid #close the progress bar window 518 | 519 | else #fastmode == 1 520 | #simpler file browser mode with less GUI features but it loads faster 521 | echocommand "rclone lsf "\""$drive:$(echo "$prefix" | sed 's|/$||g')"\""" 522 | filelist="$(rclone lsf "$drive:$(echo "$prefix" | sed 's|/$||g')" 2>&1)" 523 | [ $? != 0 ] && errorback "rclone failed to acquire a file list from $drive:$prefix!\nErrors: $filelist" 524 | kill $loader_pid #close the progress bar window 525 | 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")" 526 | #echo "$filelist" 527 | fi 528 | 529 | #array of buttons to send to file browser window 530 | buttons=() 531 | if [ ! -z "$prefix" ];then 532 | buttons+=(--button=Back!"${DIRECTORY}/icons/back.png"!"Up one level to $(echo "$drive:$(dirname "$prefix" | sed 's/^.$//g')" | sed 's/:$//g')":2) 533 | fi 534 | buttons+=(--button='Add files'!"${DIRECTORY}/icons/upload.png"!"Upload files to the current folder, or into a subfolder if one is selected.":10 \ 535 | --button=Download!"${DIRECTORY}/icons/download.png"!"Download selected item(s) to your local Downloads folder.":8 \ 536 | --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 \ 537 | --button=Link!"${DIRECTORY}/icons/link.png"!"Generate a link to publicly share the selected file/folder.":4) 538 | 539 | if [ $fastmode == 0 ];then 540 | output="$(echo "$LIST" | sed 's/&/&/g' | yad "${yadflags[@]}" --list --multiple --separator='\n' --title="CloudBuddy - browsing $(echo "$drive:$prefix" | sed 's/:$//g' | sed 's|/$||g')" \ 541 | --column=:IMG --column=Name --column=echoName:HD --column=Size:SZ --column=MimeType:HD --column=ModTime --column=IsDir:HD --column=ID:HD --print-column=3 \ 542 | --width=560 --height=400 --text="$([ ! -z "$prefix" ] && echo -e "Currently in $(echo "$prefix" | sed 's|/$||g').\r")Double-click to open files/folders." \ 543 | "${buttons[@]}" )" 544 | button=$? 545 | else 546 | output="$(echo "$filelist" | sed 's/&/&/g' | yad "${yadflags[@]}" --list --multiple --separator='\n' \ 547 | --column=:IMG --column=Name --print-column=2 \ 548 | --width=300 --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 | fi 552 | 553 | output="$(echo "$output" | grep .)" 554 | echo "Prefix is $prefix, Output is '$output'" 555 | 556 | if [ $button == 2 ];then 557 | #back button: go up a directory in prefix. 558 | prefix="$(dirname "$prefix" | sed 's/^.$//g')" 559 | elif [ $button == 4 ] && [ ! "$(echo "$output" | wc -l)" -gt 1 ];then 560 | #get link for selected 561 | if [ -z "$output" ];then 562 | warning "In order to generate a link, you must select a file or folder." 563 | else 564 | (echo "# "; sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title="CloudBuddy" \ 565 | --text="Generating link..." \ 566 | --width=300 --height=100 --center --auto-close --auto-kill \ 567 | --no-buttons 2>/dev/null & 568 | loader_pid=$! 569 | echocommand "rclone link "\""$drive:$prefix$output"\""" 570 | link="$(rclone link "$drive:$prefix$output" 2>&1)" 571 | exitcode=$? 572 | kill $loader_pid 573 | if [ $exitcode != 0 ];then 574 | warning "Failed to generate a link for $drive:$prefix$output\nErrors: $link" 575 | else 576 | yad "${yadflags[@]}" --form --columns=2 --width=400 \ 577 | --image="${DIRECTORY}/icons/$(drivetype "$drive").png" \ 578 | --text="Public link below." \ 579 | --field='' "$link" --field=Copy:FBTN "bash -c 'echo "\""$link"\"" | xclip -sel clip'" \ 580 | --button=Close:0 581 | fi 582 | fi 583 | elif [ $button == 6 ];then 584 | #move selected 585 | if [ -z "$output" ];then 586 | warning "A file or folder must be selected in order to move it!" 587 | else 588 | if [ "$(echo "$output" | wc -l)" -gt 1 ];then 589 | #multiple items selected, only display Delete option. 590 | moveto="$(echo -en "$output" | yad "${yadflags[@]}" --text-info --fontname=12 --wrap \ 591 | --width=400 --text="When multiple items are selected, only the Delete option is available."$'\n'"These items were selected:" \ 592 | --button=Cancel:1 \ 593 | --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)" 594 | button=$? 595 | else 596 | #single file/folder selected, display the usual 'Move' options. 597 | moveto="$(yad "${yadflags[@]}" --form --width=400 --text=" Enter a path to move the chosen file:" \ 598 | --field="From ":RO "$prefix$(echo "$output" | sed 's|/$||g')" \ 599 | --field="To " "$prefix$(echo "$output" | sed 's|/$||g')" \ 600 | --button=Cancel:1 \ 601 | --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 \ 602 | --button=Move!"${DIRECTORY}/icons/move.png":0)" 603 | button=$? 604 | fi 605 | 606 | if [ $button == 0 ];then 607 | #move file 608 | 609 | #remove trailing and leading slashes from output, and only keep second line 610 | moveto="$(echo "$moveto" | grep . | sed 's|^/||g' | sed 's|/$||g' | sed -n 2p)" 611 | 612 | (echo "# "; sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title="Moving..." \ 613 | --text="Moving $prefix$output"$'\n'"to $moveto..." \ 614 | --width=300 --height=100 --center --auto-close --auto-kill \ 615 | --no-buttons 2>/dev/null & 616 | loader_pid=$! 617 | echocommand "rclone moveto "\""$drive:$prefix$output"\"" "\""$drive:$moveto"\""" 618 | errors="$(rclone moveto "$drive:$prefix$output" "$drive:$moveto" 2>&1)" 619 | if [ $? != 0 ];then 620 | warning "Failed to move $prefix$output to $moveto.\nErrors: $errors" 621 | fi 622 | kill $loader_pid 623 | 624 | elif [ $button == 2 ];then 625 | #delete file 626 | 627 | #delete every file found in $output, one at a time 628 | IFS=$'\n' 629 | for file in $output ;do 630 | (echo "# "; sleep 20; echo "# This is taking longer than expected to complete."; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title="Deleting..." \ 631 | --text="Deleting $drive:$prefix$file..." \ 632 | --width=300 --height=100 --center --auto-close --auto-kill \ 633 | --no-buttons 2>/dev/null & 634 | loader_pid=$! 635 | if [[ "$file" == */ ]];then 636 | #if folder, delete folder 637 | echocommand "rclone purge "\""$drive:$prefix$file"\""" 638 | set -o pipefail 639 | errors="$(rclone purge "$drive:$prefix$file" 2>&1 | tee /dev/stderr)" 640 | exitcode=$? 641 | else 642 | echocommand "rclone deletefile "\""$drive:$prefix$file"\""" 643 | set -o pipefail 644 | errors="$(rclone deletefile "$drive:$prefix$file" 2>&1 | tee /dev/stderr)" 645 | exitcode=$? 646 | fi 647 | [ $exitcode != 0 ] && warning "Rclone failed to delete $drive:$prefix$file. Errors:\n$errors" & 648 | kill $loader_pid #close the progress bar window 649 | done 650 | fi 651 | fi 652 | elif [ $button == 8 ];then 653 | #download selected item(s) 654 | echo Download 655 | if [ -z "$output" ];then 656 | warning "At least one file or folder must be selected in order the Download button to do anything!" 657 | else 658 | destinationdir="$HOME/Downloads" 659 | 660 | "${DIRECTORY}/terminal-run" " 661 | trap 'sleep infinity' EXIT 662 | output="\""$output"\"" 663 | source "\""$0"\"" source 664 | IFS=$'\n' 665 | failed=0 666 | for file in "\$"output ;do 667 | echoprogress "\""Downloading "\"""\\""\"""\""$drive:$prefix"\$"file"\"""\\""\"""\"""\"" 668 | if [[ "\"""\$"file"\"" == */ ]];then 669 | #if downloading directory, create subdirectory on destination to preserve structure 670 | echocommand "\""rclone copy -P --stats-one-line "\"""\\""\"""\""$drive:$prefix"\$"file"\"""\\""\"""\"" "\"""\\""\"""\""$destinationdir/"\$"file"\"""\\""\"""\"""\"" 671 | #re-run on failure if it took over 10 minutes 672 | while true;do 673 | start_time="\$"(date +%s) 674 | rclone copy -P --stats-one-line "\""$drive:$prefix"\$"file"\"" "\""$destinationdir/"\$"file"\"" 675 | exitcode="\$"? 676 | end_time="\$"(date +%s) 677 | if [ "\$"exitcode == 0 ];then 678 | break 679 | elif [ "\$"exitcode != 0 ] && [ "\$"((end_time-start_time)) -ge 600 ];then 680 | echo -e '\n\e[101m\e[30mDownload failed! But it was going for 10 minutes, so trying again.\e[39m\e[49m' 681 | else 682 | break #give up with exitcode=1 683 | fi 684 | done 685 | else 686 | echocommand "\""rclone copy -P --stats-one-line "\"""\\""\"""\""$drive:$prefix"\$"file"\"""\\""\"""\"" "\"""\\""\"""\""$destinationdir"\"""\\""\"""\"""\"" 687 | rclone copy -P --stats-one-line "\""$drive:$prefix"\$"file"\"" "\""$destinationdir"\"" 688 | exitcode="\$"? 689 | fi 690 | if [ "\$"exitcode != 0 ];then 691 | failed=1 692 | fi 693 | done 694 | if [ "\$"failed == 0 ];then 695 | echo -e '\e[102m\e[30mDownload succeeded! Close this window to exit.\e[39m\e[49m' 696 | else 697 | echo -e '\n\e[101m\e[30mDownload failed! Errors above.\e[39m\e[49m' 698 | fi 699 | " "Downloading $(echo "$output" | wc -l) file$([ $(echo "$output" | wc -l) != 1 ] && echo s) to $(echo "$destinationdir" | sed "s|$HOME/|~/|g")" 700 | fi 701 | elif [ $button == 10 ] && [ ! "$(echo "$output" | wc -l)" -gt 1 ];then 702 | #upload files 703 | if [ ! -z "$output" ] && [[ "$output" == */ ]];then 704 | #if folder selected, destination is prefix/output 705 | destinationdir="$prefix$output" 706 | else 707 | destinationdir="$prefix" 708 | fi 709 | destinationdir="$(echo "$destinationdir" | sed 's|/$||g')" 710 | 711 | output="$(yad "${yadflags[@]}" --dnd --on-top --text=" Drag-n-drop files here to upload them to $(echo "$drive:$destinationdir" | sed 's/:$//g'). " \ 712 | --button=Cancel!!"Go back":1 --button=Upload!"${DIRECTORY}/icons/upload.png"!"Upload the files you have dropped onto this window":0)" 713 | button=$? 714 | #convert output from "file://" format to absolute paths format 715 | output="$(echo "$output" | sed 's|^file://||')" 716 | 717 | if [ $button == 0 ] && [ ! -z "$output" ];then 718 | #upload each file at a time with rclone 719 | "${DIRECTORY}/terminal-run" " 720 | trap 'sleep infinity' EXIT 721 | output="\""$output"\"" 722 | source "\""$0"\"" source 723 | IFS=$'\n' 724 | failed=0 725 | for file in "\$"output ;do 726 | echoprogress "\""Copying "\$"file..."\"" 727 | 728 | if [ -d "\"""\$"file"\"" ];then 729 | #if uploading directory, create subdirectory on destination to preserve structure 730 | echocommand rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir/"\$"(basename "\"""\$"file"\"")"\"" 731 | rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir/"\$"(basename "\"""\$"file"\"")"\"" 732 | exitcode="\$"? 733 | else 734 | echocommand rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir"\"" 735 | rclone copy -P --stats-one-line "\"""\$"file"\"" "\""$drive:$destinationdir"\"" 736 | exitcode="\$"? 737 | fi 738 | if [ "\$"exitcode != 0 ];then 739 | failed=1 740 | fi 741 | done 742 | if [ "\$"failed == 0 ];then 743 | echo -e '\e[102m\e[30mUpload succeeded! Close this window to exit.\e[39m\e[49m' 744 | else 745 | echo -e '\e[101m\e[30mUpload failed! Errors above.\e[39m\e[49m' 746 | fi 747 | #create yad button-box to refresh main window 748 | 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"\"" 749 | " "Uploading $(echo "$output" | wc -l) file$([ $(echo "$output" | wc -l) != 1 ] && echo s) to $(echo "$drive:$destinationdir" | sed 's/:$//g')" 750 | elif [ $button == 0 ] && [ -z "$output" ];then 751 | warning "No files were dropped onto the Upload window.\nAs a result, no files will be uploaded to $(echo "$drive:$destinationdir" | sed 's/:$//g')." 752 | fi 753 | 754 | elif [ $button == 0 ] && [ ! "$(echo "$output" | wc -l)" -gt 1 ];then 755 | #open file/folder - double-clicked 756 | if [[ "$output" == */ ]];then 757 | echobright "Folder selected: $(echo "$output" | sed 's|/$||g')" 758 | if [ ! -z $prefix ];then 759 | prefix="$prefix$output" 760 | else 761 | prefix="$output" 762 | fi 763 | else 764 | echobright "File selected: $(echo "$output")" 765 | 766 | #download file 767 | tmpdir="$(mktemp -d)" 768 | 769 | set -o pipefail 770 | (echo "# "; sleep infinity) | yad "${yadflags[@]}" --progress --pulsate --title=Downloading \ 771 | --text="Downloading:"$'\n'"$drive:$prefix$output"$'\n'"To: $tmpdir..." \ 772 | --width=300 --height=100 --center --auto-close --auto-kill \ 773 | --button=Cancel:"bash -c '$0 browsedrive "\""$drive"\$"(echo Ogo= | base64 -d)$prefix"\""; kill $$ $(list_descendants $$)'" & 774 | loader_pid=$! 775 | 776 | echocommand "rclone copy -P --stats-one-line "\""$drive:$prefix$output\""" "\""$tmpdir"\""" 777 | set -o pipefail 778 | errors="$(rclone copy -P --stats-one-line "$drive:$prefix$output" "$tmpdir" 2>&1 | tee /dev/stderr)" 779 | exitcode=$? 780 | kill $loader_pid #close the progress bar window 781 | 782 | if [ $button == 1 ];then 783 | true #cancel button clicked 784 | elif [ ! -f "$tmpdir/$output" ];then 785 | #check for output file existence 786 | warning "Rclone succeeded to download $output, but $tmpdir/$output does not exist.\nErrors: $errors" 787 | else 788 | #open file 789 | xdg-open "$tmpdir/$output" 790 | fi 791 | fi 792 | elif [ "$(echo "$output" | wc -l)" -gt 1 ] && ([ $button == 0 ] || [ $button == 4 ] || [ $button == 10 ]);then 793 | #multiple items selected 794 | warning "This button cannot work when multiple items are selected!" 795 | else #unknown button 796 | break 797 | fi 798 | done #end of interactive file browser - forever loop 799 | 800 | elif [ "$1" == webinterface ];then 801 | echobright "Web Interface" 802 | echobright "To get here from the command line, run this:\n$0 webinterface" 803 | "${DIRECTORY}/terminal-run" "trap 'sleep infinity' EXIT 804 | source "\""$0"\"" source 805 | echocommand 'rclone rcd --rc-web-gui' 806 | rclone rcd --rc-web-gui 807 | " "Running Rclone Web Interface" 808 | fi #END OF COMMAND-LINE subscripts 809 | 810 | #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. 811 | if [ ! -z "$1" ];then 812 | back 813 | fi 814 | 815 | #if rclone drive mounted anywhere, display button to unmount drives 816 | if [ ! -z "$(mount | grep fuse.rclone)" ] && [ "$usefuse" == 1 ];then 817 | unmountbutton=(--field="Unmount Drive"!"${DIRECTORY}/icons/eject.png"!"Unmount a cloud drive from your filesystem":FBTN "$0 unmountdrive") 818 | fi 819 | 820 | #if user has configured at least 1 drive, display these buttons: 821 | if [ ! -z "$remotes" ];then 822 | if [ "$usefuse" == 1 ];then 823 | mountbutton=(--field="Mount Drive"!"${DIRECTORY}/icons/mount.png"!"Connect a cloud drive to your computer like a USB drive":FBTN "$0 mountdrive") 824 | fi 825 | 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") 826 | removebutton=(--field="Remove Drive"!"${DIRECTORY}/icons/remove.png"!"Sign out of an existing cloud drive":FBTN "$0 removedrive") 827 | fi 828 | 829 | yad "${yadflags[@]}" --width=400 --form --columns=2 --no-buttons \ 830 | --field="New Drive"!"${DIRECTORY}/icons/new.png"!"Connect to a new cloud drive":FBTN "$0 newdrive" \ 831 | "${mountbutton[@]}" \ 832 | "${browsebutton[@]}" \ 833 | "${removebutton[@]}" \ 834 | "${unmountbutton[@]}" \ 835 | --field="Web Interface"!"${DIRECTORY}/icons/webinterface.png"!"Launch rclone's built-in browser interface.":FBTN "$0 webinterface" \ 836 | 837 | -------------------------------------------------------------------------------- /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 --------------------------------------------------------------------------------