Browse Source

Created symlinks. Content and style fixes.

pull/2/head
frnmst/Franco Masotti 7 years ago
parent
commit
a64566b5d2
  1. 4
      README.md
  2. 480
      gnupot
  3. 1
      gnupot
  4. 1
      setup
  5. 103
      src/config.sh
  6. 0
      src/gnupot.config.example
  7. 480
      src/gnupot.sh

4
README.md

@ -71,12 +71,10 @@ Stupid) principal.
```
$ git clone https://github.com/frnmst/gnupot.git
$ cd gnupot
$ ./config.sh
```
- Run the setup like this: `./setup`
- Be sure to have an ssh server up and running.
- Be able to connect to that server with private/public keys (i.e. passwordless).
- ~~On the server: `git init --bare --shared <repo name>`~~
- ~~On the client: `git clone <remote user>@<server>:<repo name> <local dir>`~~
- Install the packets described below.
Once you have completed all the previous points you can actually run the

480
gnupot

@ -1,480 +0,0 @@
#!/bin/bash
#
# gnupot
#
# Copyright (C) 2015 frnmst (Franco Masotti) <franco.masotti@live.com>
# <franco.masotti@student.unife.it>
#
# This file is part of GNUpot.
#
# GNUpot is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GNUpot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNUpot. If not, see <http://www.gnu.org/licenses/>.
#
# Enable automatic export of all variables form now on.
# This avoids putting "export" in front of every variable.
set -a
# Set path.
PATH="$PATH":/usr/bin
# Flock so that script is not executed more than once.
# See man 1 flock (examples section).
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
# Load configuration.
if [ -f "gnupot.config" ]; then
source gnupot.config
fi
# Macros.
# SSH arguments.
# TODO: Add -i <public key> as explicit parameter.
SSHARGS="-C -S "$gnupotSSHMasterSocketPath""
# Used for general ssh commands
SSHCONNECTCMDARGS="$SSHARGS "$gnupotServerUsername"@"$gnupotServer""
# Open master socket so that further connection will result faster
# (using multiplexing to avoid re-authentication).
SSHMASTERSOCKCMDARGS="-M -o \
ControlPersist="$gnupotSSHMasterSocketTime" \
$SSHCONNECTCMDARGS exit"
# inotifywait command macro: recursive, quiet, listen only to certain events.
INOTIFYWAITCMD="inotifywait -r -q -e modify -e attrib \
-e move -e move_self -e create -e delete -e delete_self"
GITCMD="git"
# git environment variable for ssh.
GIT_SSH_COMMAND="ssh $SSHARGS"
# General notification function.
function notifyCmd ()
{
msg="$1"
ms="$2"
notify-send -t "$ms" "$msg"
return 0
}
function syncNotify ()
{
path="$1"
dir="$2"
source="$3"
notifyCmd "GNUpot syncing $(getRelativePath "$path" \
"$dir") from "$source"" "$gnupotDefaultNotificationTime"
return 0
}
# Get relative path for local or remote changed files.
function getRelativePath ()
{
fullPath="$1"
srcDir="$2"
tmp=""
# Get relative path of file.
tmp="${fullPath##"$srcDir"}"
# Remove heading slash and return value.
echo "${tmp:1}"
return 0
}
# Resolve conflict function.
# WARING: AT THIS MOMENT THIS FUNCTION WORKS BUT IT'S VERY BASIC.
# CONFLICTING FILES ARE MERGED.
function resolveConflicts ()
{
returnedVal="$1"
action="$2"
if [ "$returnedVal" -eq 1 ]; then
notifyCmd "Resolving file conflicts." \
"$gnupotDefaultNotificationTime"
$GITCMD commit -a -m "Commit on $(date "+%s"). Handled \
conflicts"
fi
return 0
}
# Clean useless files and keep maximum user defined number of backups.
function backupAndClean ()
{
currentCommits="$1"
commitSha=""
# if <number of commits for current session> mod $KeepMaxBackups -eq 0
# and $KeepMaxBackups.
if [ $(expr "$currentCommits" % "$gnupotKeepMaxCommits") \
-eq 0 ] && [ "$gnupotKeepMaxCommits" -ne 0 ]; then
# Get sha of interest.
commitSha=$($GITCMD log -n "$gnupotKeepMaxCommits" \
| tail -n 6 | grep commit | awk ' { print $2 } ')
# From man git-checkout:
# Create a new orphan branch, named <new_branch>, started from
# <start_point> and switch to it.
$GITCMD checkout --orphan tmp "$commitSha"
# Change old commit.
$GITCMD commit -m "Truncated history on $(date "+%s")"
# From man git-rebase
# Forward-port local commits to the updated upstream head.
$GITCMD rebase --onto tmp "$commitSha" master
# Delete tmp branch.
$GITCMD branch -D tmp
# Garbage collector for stuff older than 1d.
# TODO better.
$GITCMD gc --auto --prune=1d
$GITCMD push -f origin master
else
$GITCMD push origin master
fi
return 0
}
function getCommitNumber ()
{
if [ ! -f "$gnupotCommitNumberFilePath" ]; then
echo 1 > "$gnupotCommitNumberFilePath"
echo 1
else
cat "$gnupotCommitNumberFilePath"
fi
return 0
}
# Both client and server threads execute this function.
function sharedSyncActions ()
{
# Do all git operations in the correct directory.
cd "$gnupotLocalDir"
$GITCMD add -A
$GITCMD commit -a -m "Commit on $(date "+%s")"
# Always pull from server first then check for conflicts using return
# value.
$GITCMD pull origin master
resolveConflicts "$?" "<smt>"
currentCommits=$(getCommitNumber)
# To be able to use this: git config --system
# receive.denyNonFastForwards true
backupAndClean "$currentCommits"
# Update commit number.
echo $(($currentCommits+1)) > "$gnupotCommitNumberFilePath"
# Go back to previous dir.
cd "$OLDPWD"
return 0
}
# Main file syncronization function.
# This is executed inside a critical section.
function syncOperation ()
{
source="$1"
path="$2"
sleep "$gnupotTimeToWaitForOtherChanges"
if [ "$source" == "server" ]; then
syncNotify "$path" "$gnupotRemoteDir" "server"
else
syncNotify "$path" "$gnupotLocalDir" "client"
fi
# Do the syncing.
sharedSyncActions
# OK.
notifyCmd "Done." "$gnupotDefaultNotificationTime"
return 0
}
# Kill program if local and/or remote directories do not exist.
function checkDirExistence ()
{
input="$1"
if [ "$input" -ne 0 ]; then
errMsg="Local and/or remote directory does/do not exist."
echo -en "$errMsg\n"
notifyCmd "$errMsg" "$gnupotDefaultNotificationTime"
kill -s SIGINT 0
fi
return 0
}
function checkServerDirExistence ()
{
# Check if remote directory exists.
dirNotExists=$(ssh $SSHCONNECTCMDARGS "if [ ! -d $gnupotRemoteDir ]; \
then echo 1; else echo 0; fi")
checkDirExistence "$dirNotExists"
return 0
}
function checkClientDirExistence ()
{
# Check if local directory exists.
dirNotExists=$(if [ ! -d "$gnupotLocalDir" ]; \
then echo 1; else echo 0; fi)
checkDirExistence "$dirNotExists"
return 0
}
function callSync ()
{
source="$1"
# Check if the other thread is in the critical section.
# This avoids a two way file update. Example: if a file is
# modified on the client, it is sent immediately to the server.
# However the server thead detects changes and so an unecessary
# pull is made from the server.
# So there are two types of locks: one between the round
# brackets and the other one is made by the if clause.
if [ $(cat "$gnupotLockFilePath") -eq 0 ]; then
# Open a subshell for critical section.
(
# Acquire lockfile.
echo 1 > "$gnupotLockFilePath"
# While not acquire lock:
# while [ ! flock -n 1024 ]; do :; done
# is the same as the following line:
flock -x "$FD"
syncOperation "$source" "$path"
# End critical section.
) {FD}>>"$gnupotLockFilePath"
# Get first valid file descriptor from a bash builtin.
# Free lockfile.
echo 0 > "$gnupotLockFilePath"
else
# Wait some time to avoid unecessary loops. This happens if
# there are lots of files to be transferred
sleep "$gnupotTimeToWaitForOtherChanges"
fi
return 0
}
# Server sync thread.
function syncS ()
{
# return/exit when signal{s} is/are received.
trap "return 0" SIGINT SIGTERM
# Open master ssh socket.
ssh $SSHMASTERSOCKCMDARGS 2>&-
checkServerDirExistence
while true; do
# Listen for changes on server
path=$(ssh $SSHCONNECTCMDARGS "$INOTIFYWAITCMD" \
"$gnupotRemoteDir" | awk ' { print $1 $3 } ')
callSync "server"
done
}
# Client sync thread.
function syncC ()
{
trap "return 0" SIGINT SIGTERM
checkClientDirExistence
while true; do
path=$($INOTIFYWAITCMD --exclude .git "$gnupotLocalDir" \
| awk ' { print $1 $3 } ')
callSync "client"
done
}
# Signal handler function.
function sigHandler ()
{
echo -en "GNUpot killed\n" 1>&2
# Kill master ssh socket (this will kill any ssh connection associated
# with it). Also disable stderr output for this command with "2>&-".
ssh -O exit -S "$gnupotSSHMasterSocketPath" "$gnupotServer" 2>&- &
# Kill all the processes of this group.
kill -s SIGINT 0
return 0
}
# Clean before exiting.
function clean ()
{
# Wait for client and server threads before exiting.
wait "$srvPid" "$cliPid"
return 0
}
# Main function that runs in background.
function main ()
{
# Enable signal interpretation to kill all subshells
trap "sigHandler" SIGINT SIGTERM
notifyCmd "GNUpot starting..." "$gnupotDefaultNotificationTime"
echo 0 > "$gnupotLockFilePath"
# Listen from server and send to client.
syncS &
srvPid="$!"
# Listen from client and send to server.
syncC &
cliPid="$!"
clean
notifyCmd "GNUpot stopped..." "$gnupotDefaultNotificationTime"
return 0
}
# Call main function as spawned shell (execute and return control to the
# shell).
main &
exit 0
# SOME IDEAS FOR A BETTER FILE CONFLICT RESOLVER FUNCTION.
#
# if [ "$returnedVal" -eq 1 ]; then
# notifyCmd "Resolving file conflicts." \
#"$gnupotDefaultNotificationTime"
# SOME IDEAS.
# Server and clients have master and tmp branches.
# If there is no problems, branches are equal.
# Otherwise:
#
# git pull origin tmp
# <compare master & tmp branches: get conflicting files list>
# <move master files>
# git checkout ??-b?? tmp # change to tmp branch
# <move tmp files>
# <merge branches in master branch without deleting tmp branch>
# git push origin master
# git push origin tmp
# END OF SOME IDEAS.
# IT DOES NOT WORK BUT THIS IS THE previous IDEA:
# get file conflict list
#conflictList=$(git diff --name-only --diff-filter=U)
# Create locally tmp branch if it doen not exist.
#git checkout -b tmp
#for conflict in $conflictList; do
# move conflicting files to new names
# mv "$conflict" "$conflict"."$REMOTE_USR"."$DATE"
#done
#git commit -a -m "??resolved??"
# change to master branch
#git checkout master
#for conflict in $conflictList; do
# move conflicting files to new names
# mv "$conflict" "$conflict"."$REMOTE_USR"."$DATE"
#done
# tmp U master
#git merge tmp
#if [ "$action" == "push" ]; then
#git push origin master
#else
# :
#fi
#notifyCmd "File conflicts resolved." \
#"$gnupotDefaultNotificationTime"
# echo 1
# fi
# echo 0

1
gnupot

@ -0,0 +1 @@
src/gnupot.sh

1
setup

@ -0,0 +1 @@
src/config.sh

103
config.sh → src/config.sh

@ -31,13 +31,13 @@ PATH="$PATH":/usr/bin
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
BACKTITLE="GNUpot_setup"
# Macros.
DIALOG="dialog --clear --stdout --help-button --backtitle $BACKTITLE"
HELPFILE="README.md"
BACKTITLE="GNUpot_setup"
DIALOG="dialog --clear --stdout --hfile $HELPFILE --backtitle $BACKTITLE"
# Variables.
options=""
endSetup="0"
# Required variables.
Server=""
ServerUsername=""
@ -55,32 +55,57 @@ LockFilePath="$HOME/.lockfile"
CommitNumberFilePath="$HOME/.commitNums"
function getConfig ()
function displayForm ()
{
options=$($DIALOG --title "GNUpot setup" \
--form "Use arrow up and down to move between fields" \
title="$1"
arg="$2"
action="$3"
opts=""
retval=""
opts=$($DIALOG --title "$title" \
--form "$arg" \
20 90 0 \
"Server address or hostname:" 1 1 "" 1 35 50 0 \
"Remote user name:" 2 1 "" 2 35 50 0 \
"Remote directory path:" 3 1 "$RemoteDir" 3 35 50 0 \
"Local directory full path:" 4 1 "$LocalDir" 4 35 50 0 \
"Backups to keep (0 = keep all):" 5 1 "$KeepMaxCommits" 5 35 50 0 \
"Local home full path:" 6 1 "$LocalHome" 6 35 50 0 \
"Remote home full path:" 7 1 "$RemoteHome" 7 35 50 0 \
"Server address or hostname:" 1 1 "$Server" 1 35 $action \
0 \
"Remote user name:" 2 1 "$ServerUsername" 2 35 $action \
0 \
"Remote directory path:" 3 1 "$RemoteDir" 3 35 $action \
0 \
"Local directory full path:" 4 1 "$LocalDir" 4 35 $action 0 \
"Backups to keep (0 = keep all):" 5 1 "$KeepMaxCommits" 5 35 $action \
0 \
"Local home full path:" 6 1 "$LocalHome" 6 35 $action \
0 \
"Remote home full path:" 7 1 "$RemoteHome" 7 35 $action \
0 \
"Time to wait for changes (s):" 8 1 "$TimeToWaitForOtherChanges" \
8 35 50 0 \
8 35 $action 0 \
"SSH Msster Socket Path:" 9 1 "$SSHMasterSocketPath" \
9 35 50 0 \
9 35 $action 0 \
"SSH socket keepalive time (min):" 10 1 "$SSHMasterSocketTime" \
10 35 50 0 \
10 35 $action 0 \
"Event notification time:" 11 1 "$DefaultNotificationTime" \
11 35 50 0 \
11 35 $action 0 \
"Lock file path:" 12 1 "$LockFilePath" \
12 35 50 0 \
12 35 $action 0 \
"Commit number file path:" 13 1 "$CommitNumberFilePath" \
13 35 50 0 \
13 35 $action 0 \
)
retval="$?"
echo "$opts"
return "$retval"
}
function getConfig ()
{
options=$(displayForm "GNUpot setup" "Use arrow up and down \
to move between fields" "50")
return 0
@ -120,27 +145,12 @@ function verifyConfig ()
function summary ()
{
$DIALOG --title "GNUpot setup summary" --msgbox "\
$(echo -en "Required settings\n\
=================\n\
Server:\t"$Server"\n\
Remote user:\t"$ServerUsername"\n\
Remote destination directory:\t"$RemoteDir"\n\
Local destination directory:\t"$LocalDir"\n\
Number of backups (0 means keep all commits):\t"$KeepMaxCommits"\n\
Local home full path:\t"$LocalHome"\n\
Remote home full path:\t"$RemoteHome"\n\n\
Optional settings\n\
=================\n\
Time to wait for changes:\t"$TimeToWaitForOtherChanges"\n\
SSH Msster Socket Path:\t"$SSHMasterSocketPath"\n\
SSH master socket keepalive time:\t"$SSHMasterSocketTime"\n\
Event notification time:\t"$DefaultNotificationTime"\n\
Lock file path:\t"$LockFilePath"\n\
Commit number file path:\t"$CommitNumberFilePath"\n\n\
You can change these settings any time by editing <somefile>.\n\
")\
" 25 90
displayForm "GNUpot setup summary" "Are the displayed values \
correct?" "0"
if [ "$?" -ne 0 ]; then
return 1
fi
return 0
@ -223,15 +233,22 @@ gnupotCommitNumberFilePath=\""$CommitNumberFilePath"\"\n\
function main ()
{
while [ "$endSetup" -eq 0 ]; do
endSetup="0"
infoOk="0"
while [ "$endSetup" -eq 0 ] && [ "$infoOk" -eq 0 ]; do
getConfig
verifyConfig
if [ "$?" -eq 0 ]; then
strTok
summary
testInfo
if [ "$?" -eq 0 ]; then
endSetup=1
infoOk=1
testInfo
if [ "$?" -eq 0 ]; then
endSetup=1
fi
fi
fi
done

0
gnupot.config.example → src/gnupot.config.example

480
src/gnupot.sh

@ -0,0 +1,480 @@
#!/bin/bash
#
# gnupot
#
# Copyright (C) 2015 frnmst (Franco Masotti) <franco.masotti@live.com>
# <franco.masotti@student.unife.it>
#
# This file is part of GNUpot.
#
# GNUpot is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GNUpot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNUpot. If not, see <http://www.gnu.org/licenses/>.
#
# Enable automatic export of all variables form now on.
# This avoids putting "export" in front of every variable.
set -a
# Set path.
PATH="$PATH":/usr/bin
# Flock so that script is not executed more than once.
# See man 1 flock (examples section).
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
# Load configuration.
if [ -f "gnupot.config" ]; then
source gnupot.config
fi
# Macros.
# SSH arguments.
# TODO: Add -i <public key> as explicit parameter.
SSHARGS="-C -S "$gnupotSSHMasterSocketPath""
# Used for general ssh commands
SSHCONNECTCMDARGS="$SSHARGS "$gnupotServerUsername"@"$gnupotServer""
# Open master socket so that further connection will result faster
# (using multiplexing to avoid re-authentication).
SSHMASTERSOCKCMDARGS="-M -o \
ControlPersist="$gnupotSSHMasterSocketTime" \
$SSHCONNECTCMDARGS exit"
# inotifywait command macro: recursive, quiet, listen only to certain events.
INOTIFYWAITCMD="inotifywait -r -q -e modify -e attrib \
-e move -e move_self -e create -e delete -e delete_self"
GITCMD="git"
# git environment variable for ssh.
GIT_SSH_COMMAND="ssh $SSHARGS"
# General notification function.
function notifyCmd ()
{
msg="$1"
ms="$2"
notify-send -t "$ms" "$msg"
return 0
}
function syncNotify ()
{
path="$1"
dir="$2"
source="$3"
notifyCmd "GNUpot syncing $(getRelativePath "$path" \
"$dir") from "$source"" "$gnupotDefaultNotificationTime"
return 0
}
# Get relative path for local or remote changed files.
function getRelativePath ()
{
fullPath="$1"
srcDir="$2"
tmp=""
# Get relative path of file.
tmp="${fullPath##"$srcDir"}"
# Remove heading slash and return value.
echo "${tmp:1}"
return 0
}
# Resolve conflict function.
# WARING: AT THIS MOMENT THIS FUNCTION WORKS BUT IT'S VERY BASIC.
# CONFLICTING FILES ARE MERGED.
function resolveConflicts ()
{
returnedVal="$1"
action="$2"
if [ "$returnedVal" -eq 1 ]; then
notifyCmd "Resolving file conflicts." \
"$gnupotDefaultNotificationTime"
$GITCMD commit -a -m "Commit on $(date "+%s"). Handled \
conflicts"
fi
return 0
}
# Clean useless files and keep maximum user defined number of backups.
function backupAndClean ()
{
currentCommits="$1"
commitSha=""
# if <number of commits for current session> mod $KeepMaxBackups -eq 0
# and $KeepMaxBackups.
if [ $(expr "$currentCommits" % "$gnupotKeepMaxCommits") \
-eq 0 ] && [ "$gnupotKeepMaxCommits" -ne 0 ]; then
# Get sha of interest.
commitSha=$($GITCMD log -n "$gnupotKeepMaxCommits" \
| tail -n 6 | grep commit | awk ' { print $2 } ')
# From man git-checkout:
# Create a new orphan branch, named <new_branch>, started from
# <start_point> and switch to it.
$GITCMD checkout --orphan tmp "$commitSha"
# Change old commit.
$GITCMD commit -m "Truncated history on $(date "+%s")"
# From man git-rebase
# Forward-port local commits to the updated upstream head.
$GITCMD rebase --onto tmp "$commitSha" master
# Delete tmp branch.
$GITCMD branch -D tmp
# Garbage collector for stuff older than 1d.
# TODO better.
$GITCMD gc --auto --prune=1d
$GITCMD push -f origin master
else
$GITCMD push origin master
fi
return 0
}
function getCommitNumber ()
{
if [ ! -f "$gnupotCommitNumberFilePath" ]; then
echo 1 > "$gnupotCommitNumberFilePath"
echo 1
else
cat "$gnupotCommitNumberFilePath"
fi
return 0
}
# Both client and server threads execute this function.
function sharedSyncActions ()
{
# Do all git operations in the correct directory.
cd "$gnupotLocalDir"
$GITCMD add -A
$GITCMD commit -a -m "Commit on $(date "+%s")"
# Always pull from server first then check for conflicts using return
# value.
$GITCMD pull origin master
resolveConflicts "$?" "<smt>"
currentCommits=$(getCommitNumber)
# To be able to use this: git config --system
# receive.denyNonFastForwards true
backupAndClean "$currentCommits"
# Update commit number.
echo $(($currentCommits+1)) > "$gnupotCommitNumberFilePath"
# Go back to previous dir.
cd "$OLDPWD"
return 0
}
# Main file syncronization function.
# This is executed inside a critical section.
function syncOperation ()
{
source="$1"
path="$2"
sleep "$gnupotTimeToWaitForOtherChanges"
if [ "$source" == "server" ]; then
syncNotify "$path" "$gnupotRemoteDir" "server"
else
syncNotify "$path" "$gnupotLocalDir" "client"
fi
# Do the syncing.
sharedSyncActions
# OK.
notifyCmd "Done." "$gnupotDefaultNotificationTime"
return 0
}
# Kill program if local and/or remote directories do not exist.
function checkDirExistence ()
{
input="$1"
if [ "$input" -ne 0 ]; then
errMsg="Local and/or remote directory does/do not exist."
echo -en "$errMsg\n"
notifyCmd "$errMsg" "$gnupotDefaultNotificationTime"
kill -s SIGINT 0
fi
return 0
}
function checkServerDirExistence ()
{
# Check if remote directory exists.
dirNotExists=$(ssh $SSHCONNECTCMDARGS "if [ ! -d $gnupotRemoteDir ]; \
then echo 1; else echo 0; fi")
checkDirExistence "$dirNotExists"
return 0
}
function checkClientDirExistence ()
{
# Check if local directory exists.
dirNotExists=$(if [ ! -d "$gnupotLocalDir" ]; \
then echo 1; else echo 0; fi)
checkDirExistence "$dirNotExists"
return 0
}
function callSync ()
{
source="$1"
# Check if the other thread is in the critical section.
# This avoids a two way file update. Example: if a file is
# modified on the client, it is sent immediately to the server.
# However the server thead detects changes and so an unecessary
# pull is made from the server.
# So there are two types of locks: one between the round
# brackets and the other one is made by the if clause.
if [ $(cat "$gnupotLockFilePath") -eq 0 ]; then
# Open a subshell for critical section.
(
# Acquire lockfile.
echo 1 > "$gnupotLockFilePath"
# While not acquire lock:
# while [ ! flock -n 1024 ]; do :; done
# is the same as the following line:
flock -x "$FD"
syncOperation "$source" "$path"
# End critical section.
) {FD}>>"$gnupotLockFilePath"
# Get first valid file descriptor from a bash builtin.
# Free lockfile.
echo 0 > "$gnupotLockFilePath"
else
# Wait some time to avoid unecessary loops. This happens if
# there are lots of files to be transferred
sleep "$gnupotTimeToWaitForOtherChanges"
fi
return 0
}
# Server sync thread.
function syncS ()
{
# return/exit when signal{s} is/are received.
trap "return 0" SIGINT SIGTERM
# Open master ssh socket.
ssh $SSHMASTERSOCKCMDARGS 2>&-
checkServerDirExistence
while true; do
# Listen for changes on server
path=$(ssh $SSHCONNECTCMDARGS "$INOTIFYWAITCMD" \
"$gnupotRemoteDir" | awk ' { print $1 $3 } ')
callSync "server"
done
}
# Client sync thread.
function syncC ()
{
trap "return 0" SIGINT SIGTERM
checkClientDirExistence
while true; do
path=$($INOTIFYWAITCMD --exclude .git "$gnupotLocalDir" \
| awk ' { print $1 $3 } ')
callSync "client"
done
}
# Signal handler function.
function sigHandler ()
{
echo -en "GNUpot killed\n" 1>&2
# Kill master ssh socket (this will kill any ssh connection associated
# with it). Also disable stderr output for this command with "2>&-".
ssh -O exit -S "$gnupotSSHMasterSocketPath" "$gnupotServer" 2>&- &
# Kill all the processes of this group.
kill -s SIGINT 0
return 0
}
# Clean before exiting.
function clean ()
{
# Wait for client and server threads before exiting.
wait "$srvPid" "$cliPid"
return 0
}
# Main function that runs in background.
function main ()
{
# Enable signal interpretation to kill all subshells
trap "sigHandler" SIGINT SIGTERM
notifyCmd "GNUpot starting..." "$gnupotDefaultNotificationTime"
echo 0 > "$gnupotLockFilePath"
# Listen from server and send to client.
syncS &
srvPid="$!"
# Listen from client and send to server.
syncC &
cliPid="$!"
clean
notifyCmd "GNUpot stopped..." "$gnupotDefaultNotificationTime"
return 0
}
# Call main function as spawned shell (execute and return control to the
# shell).
main &
exit 0
# SOME IDEAS FOR A BETTER FILE CONFLICT RESOLVER FUNCTION.
#
# if [ "$returnedVal" -eq 1 ]; then
# notifyCmd "Resolving file conflicts." \
#"$gnupotDefaultNotificationTime"
# SOME IDEAS.
# Server and clients have master and tmp branches.
# If there is no problems, branches are equal.
# Otherwise:
#
# git pull origin tmp
# <compare master & tmp branches: get conflicting files list>
# <move master files>
# git checkout ??-b?? tmp # change to tmp branch
# <move tmp files>
# <merge branches in master branch without deleting tmp branch>
# git push origin master
# git push origin tmp
# END OF SOME IDEAS.
# IT DOES NOT WORK BUT THIS IS THE previous IDEA:
# get file conflict list
#conflictList=$(git diff --name-only --diff-filter=U)
# Create locally tmp branch if it doen not exist.
#git checkout -b tmp
#for conflict in $conflictList; do
# move conflicting files to new names
# mv "$conflict" "$conflict"."$REMOTE_USR"."$DATE"
#done
#git commit -a -m "??resolved??"
# change to master branch
#git checkout master
#for conflict in $conflictList; do
# move conflicting files to new names
# mv "$conflict" "$conflict"."$REMOTE_USR"."$DATE"
#done
# tmp U master
#git merge tmp
#if [ "$action" == "push" ]; then
#git push origin master
#else
# :
#fi
#notifyCmd "File conflicts resolved." \
#"$gnupotDefaultNotificationTime"
# echo 1
# fi
# echo 0