├── 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 | 
3 | # CloudBuddy
4 | Bash-based GUI client for [`rclone`](https://rclone.org/). This was written by Botspot on 7/29/2021.
5 | 
6 |
7 | ## Installation:
8 | [](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 | 
31 | 2. Choose a name for the new cloud drive, and select which type of drive it is.
32 | 
33 | 3. Within seconds, a web browser will appear for authorization.
34 | 
35 | 4. That's it!
36 | ## Disconnecting from a cloud drive
37 | 1. In the main window, click this button:
38 | 
39 | 2. Choose a cloud drive to sign out of.
40 | 
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 | 
46 | 2. Select a drive to continue.
47 | 
48 | 3. Choose an empty directory to mount the cloud drive to.
49 | 
50 | 4. Wait for it...
51 | 
52 | 5. Done!
53 | ## Unmounting a drive
54 | 1. In the main window, click this button:
55 | 
56 | 2. Choose a drive to be unmounted.
57 | 
58 | 3. Done!
59 | ## Browsing a drive
60 | 1. In the main window, click this button:
61 | 
62 | 2. Select a drive to continue.
63 | 
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 | 
66 | The file browser is capable of:
67 | - **Uploading** files or folders with this handy drag-n-drop window:
68 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------