├── man ├── man1 │ ├── index.txt │ ├── doubledown.1.ronn │ ├── doubledown-fsevents.1.ronn │ ├── doubledown-inotify.1.ronn │ ├── doubledown-fsevents.1 │ ├── doubledown-inotify.1 │ └── doubledown.1 └── index.txt ├── .gitignore ├── control.m4 ├── HACKING ├── LICENSE ├── README.md ├── bin ├── doubledown-inotify ├── doubledown └── doubledown-fsevents └── Makefile /man/man1/index.txt: -------------------------------------------------------------------------------- 1 | ../index.txt -------------------------------------------------------------------------------- /man/man1/doubledown.1.ronn: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gh-pages 2 | package 3 | *.html 4 | *.pkg 5 | *.tar.gz 6 | *.deb 7 | control 8 | -------------------------------------------------------------------------------- /control.m4: -------------------------------------------------------------------------------- 1 | Package: doubledown 2 | Version: __VERSION__-1 3 | Section: devel 4 | Priority: optional 5 | Essential: no 6 | Architecture: all 7 | Depends: inotify-tools 8 | Maintainer: Richard Crowley 9 | Description: Sync local changes to a remote directory. 10 | -------------------------------------------------------------------------------- /man/index.txt: -------------------------------------------------------------------------------- 1 | doubledown(1) http://devstructure.github.com/doubledown/doubledown.1.html 2 | doubledown-fsevents(1) http://devstructure.github.com/doubledown/doubledown-fsevents.1.html 3 | doubledown-inotify(1) http://devstructure.github.com/doubledown/doubledown-inotify.1.html 4 | 5 | ssh-agent(1) http://man.cx/ssh-agent(1) 6 | ssh(1) http://man.cx/ssh(1) 7 | rsync(1) http://man.cx/rsync(1) 8 | inotifywait(1) http://man.cx/inotifywait(1) 9 | -------------------------------------------------------------------------------- /HACKING: -------------------------------------------------------------------------------- 1 | Hacking on Doubledown 2 | ===================== 3 | 4 | Mac OS X 5 | -------- 6 | 7 | Everything you need comes with 10.5 and newer. 8 | 9 | Linux 10 | ----- 11 | 12 | The package requires `inotify-tools` and the build process requires 13 | `debra`, `git-core`, and `m4`. `inotify-tools` is available in Ubuntu 14 | 10.04 Lucid Lynx. `debra` is available from DevStructure. `git-core` 15 | and `m4` are available basically everywhere. 16 | 17 | echo "deb http://packages.devstructure.com/ lucid main" \ 18 | >/etc/apt/sources.list.d/devstructure.list 19 | wget -O - http://packages.devstructure.com/keyring.gpg | apt-key add - 20 | apt-get update 21 | apt-get -y install debra git-core inotify-tools m4 22 | -------------------------------------------------------------------------------- /man/man1/doubledown-fsevents.1.ronn: -------------------------------------------------------------------------------- 1 | doubledown-fsevents(1) -- sync local changes to a remote directory 2 | ================================================================== 3 | 4 | ## SYNOPSIS 5 | 6 | `doubledown-fsevents` _local_ [_user_@]_server_:_remote_ 7 | 8 | ## DESCRIPTION 9 | 10 | `doubledown-fsevents` watches _local_ using `FSEvents`, relaying changes to the _remote_ directory on _server_. 11 | 12 | Directories created within _local_ are created remotely using ssh(1). Files and directories removed within _local_ are removed remotely using ssh(1). Files that are created or changed are uploaded using rsync(1). 13 | 14 | This program requires Mac OS X 10.5 or higher. 15 | 16 | ## OPTIONS 17 | 18 | * `-h`, `--help`: 19 | Show a help message. 20 | 21 | ## THEME SONG 22 | 23 | The Arcade Fire - "The Suburbs" 24 | 25 | ## AUTHOR 26 | 27 | Richard Crowley 28 | 29 | ## SEE ALSO 30 | 31 | `doubledown` was written to make it easier for DevStructure users to use Textmate and other IDEs but it's far from the only way to skin the cat. See for more options. 32 | -------------------------------------------------------------------------------- /man/man1/doubledown-inotify.1.ronn: -------------------------------------------------------------------------------- 1 | doubledown-inofity(1) -- sync local changes to a remote directory 2 | ================================================================= 3 | 4 | ## SYNOPSIS 5 | 6 | `doubledown-inotify` _local_ [_user_@]_server_:_remote_ 7 | 8 | ## DESCRIPTION 9 | 10 | `doubledown-inotify` watches _local_ using `inotifywait`(1), relaying changes to the _remote_ directory on _server_. 11 | 12 | Directories created within _local_ are created remotely using ssh(1). Files and directories removed within _local_ are removed remotely using ssh(1). Files that are created or changed are uploaded using rsync(1). 13 | 14 | This program requires Linux with a kernel newer than 2.6.13. 15 | 16 | ## OPTIONS 17 | 18 | * `-h`, `--help`: 19 | Show a help message. 20 | 21 | ## THEME SONG 22 | 23 | The Arcade Fire - "The Suburbs" 24 | 25 | ## AUTHOR 26 | 27 | Richard Crowley 28 | 29 | ## SEE ALSO 30 | 31 | `doubledown` was written to make it easier for DevStructure users to use Textmate and other IDEs but it's far from the only way to skin the cat. See for more options. 32 | -------------------------------------------------------------------------------- /man/man1/doubledown-fsevents.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "DOUBLEDOWN\-FSEVENTS" "1" "December 2011" "DevStructure" "Doubledown" 5 | . 6 | .SH "NAME" 7 | \fBdoubledown\-fsevents\fR \- sync local changes to a remote directory 8 | . 9 | .SH "SYNOPSIS" 10 | \fBdoubledown\-fsevents\fR \fIlocal\fR [\fIuser\fR@]\fIserver\fR:\fIremote\fR 11 | . 12 | .SH "DESCRIPTION" 13 | \fBdoubledown\-fsevents\fR watches \fIlocal\fR using \fBFSEvents\fR, relaying changes to the \fIremote\fR directory on \fIserver\fR\. 14 | . 15 | .P 16 | Directories created within \fIlocal\fR are created remotely using ssh(1)\. Files and directories removed within \fIlocal\fR are removed remotely using ssh(1)\. Files that are created or changed are uploaded using rsync(1)\. 17 | . 18 | .P 19 | This program requires Mac OS X 10\.5 or higher\. 20 | . 21 | .SH "OPTIONS" 22 | . 23 | .TP 24 | \fB\-h\fR, \fB\-\-help\fR 25 | Show a help message\. 26 | . 27 | .SH "THEME SONG" 28 | The Arcade Fire \- "The Suburbs" 29 | . 30 | .SH "AUTHOR" 31 | Richard Crowley \fIrichard@devstructure\.com\fR 32 | . 33 | .SH "SEE ALSO" 34 | \fBdoubledown\fR was written to make it easier for DevStructure users to use Textmate and other IDEs but it\'s far from the only way to skin the cat\. See \fIhttp://docs\.devstructure\.com/working_remotely\fR for more options\. 35 | -------------------------------------------------------------------------------- /man/man1/doubledown-inotify.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "DOUBLEDOWN\-INOFITY" "1" "December 2011" "DevStructure" "Doubledown" 5 | . 6 | .SH "NAME" 7 | \fBdoubledown\-inofity\fR \- sync local changes to a remote directory 8 | . 9 | .SH "SYNOPSIS" 10 | \fBdoubledown\-inotify\fR \fIlocal\fR [\fIuser\fR@]\fIserver\fR:\fIremote\fR 11 | . 12 | .SH "DESCRIPTION" 13 | \fBdoubledown\-inotify\fR watches \fIlocal\fR using \fBinotifywait\fR(1), relaying changes to the \fIremote\fR directory on \fIserver\fR\. 14 | . 15 | .P 16 | Directories created within \fIlocal\fR are created remotely using ssh(1)\. Files and directories removed within \fIlocal\fR are removed remotely using ssh(1)\. Files that are created or changed are uploaded using rsync(1)\. 17 | . 18 | .P 19 | This program requires Linux with a kernel newer than 2\.6\.13\. 20 | . 21 | .SH "OPTIONS" 22 | . 23 | .TP 24 | \fB\-h\fR, \fB\-\-help\fR 25 | Show a help message\. 26 | . 27 | .SH "THEME SONG" 28 | The Arcade Fire \- "The Suburbs" 29 | . 30 | .SH "AUTHOR" 31 | Richard Crowley \fIrichard@devstructure\.com\fR 32 | . 33 | .SH "SEE ALSO" 34 | \fBdoubledown\fR was written to make it easier for DevStructure users to use Textmate and other IDEs but it\'s far from the only way to skin the cat\. See \fIhttp://docs\.devstructure\.com/working_remotely\fR for more options\. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010 Richard Crowley. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY RICHARD CROWLEY ``AS IS'' AND ANY EXPRESS 16 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL RICHARD CROWLEY OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 25 | THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | The views and conclusions contained in the software and documentation 28 | are those of the authors and should not be interpreted as representing 29 | official policies, either expressed or implied, of Richard Crowley. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | doubledown(1) -- sync local changes to a remote directory 2 | ========================================================= 3 | 4 | ## SYNOPSIS 5 | 6 | `doubledown` [`-i` _identity_] _local_ [_user_@]_server_:_remote_ 7 | 8 | ## DESCRIPTION 9 | 10 | `doubledown` brings _local_ and _remote_ on _server_ into sync and then executes doubledown-fsevents(1) to watch _local_ for changes. 11 | 12 | An ssh-agent(1) is started if one cannot be found. Because `doubledown` will connect to _server_ many times over its life, you must use an SSH key pair to authenticate. If you have not created an SSH key pair, do so with the following commands: 13 | 14 | ssh-keygen -t rsa -b 2048 -f $HOME/.ssh/id_rsa 15 | ssh "echo $(cat $HOME/.ssh/id_rsa.pub) >>.ssh/authorized_keys" 16 | 17 | When `doubledown` is run, rsync(1) is used to first download all files in _remote_ on _server_ that do not exist in _local_, thus no local changes will be clobbered. It then uploads any local changes. Finally, it executes doubledown-fsevents(1). 18 | 19 | ## OPTIONS 20 | 21 | * `-i` _identity_: 22 | Use a non-standard identity (private key). Analogous to the `-i` option to `ssh`(1). 23 | * `-h`, `--help`: 24 | Show a help message. 25 | 26 | ## THEME SONG 27 | 28 | The Arcade Fire - "The Suburbs" 29 | 30 | ## AUTHOR 31 | 32 | Richard Crowley 33 | 34 | ## SEE ALSO 35 | 36 | `doubledown` was written to make it easier for DevStructure users to use Textmate and other IDEs but it's far from the only way to skin the cat. 37 | 38 | doubledown-fsevents(1) watches a directory and relays changes. It is called by `doubledown`. 39 | 40 | The source code for `doubledown` is available at . 41 | -------------------------------------------------------------------------------- /man/man1/doubledown.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "DOUBLEDOWN" "1" "December 2011" "DevStructure" "Doubledown" 5 | . 6 | .SH "NAME" 7 | \fBdoubledown\fR \- sync local changes to a remote directory 8 | . 9 | .SH "SYNOPSIS" 10 | \fBdoubledown\fR [\fB\-i\fR \fIidentity\fR] \fIlocal\fR [\fIuser\fR@]\fIserver\fR:\fIremote\fR 11 | . 12 | .SH "DESCRIPTION" 13 | \fBdoubledown\fR brings \fIlocal\fR and \fIremote\fR on \fIserver\fR into sync and then executes doubledown\-fsevents(1) to watch \fIlocal\fR for changes\. 14 | . 15 | .P 16 | An ssh\-agent(1) is started if one cannot be found\. Because \fBdoubledown\fR will connect to \fIserver\fR many times over its life, you must use an SSH key pair to authenticate\. If you have not created an SSH key pair, do so with the following commands: 17 | . 18 | .IP "" 4 19 | . 20 | .nf 21 | 22 | ssh\-keygen \-t rsa \-b 2048 \-f $HOME/\.ssh/id_rsa 23 | ssh "echo $(cat $HOME/\.ssh/id_rsa\.pub) >\.ssh/authorized_keys" 24 | . 25 | .fi 26 | . 27 | .IP "" 0 28 | . 29 | .P 30 | When \fBdoubledown\fR is run, rsync(1) is used to first download all files in \fIremote\fR on \fIserver\fR that do not exist in \fIlocal\fR, thus no local changes will be clobbered\. It then uploads any local changes\. Finally, it executes doubledown\-fsevents(1)\. 31 | . 32 | .SH "OPTIONS" 33 | . 34 | .TP 35 | \fB\-i\fR \fIidentity\fR 36 | Use a non\-standard identity (private key)\. Analogous to the \fB\-i\fR option to \fBssh\fR(1)\. 37 | . 38 | .TP 39 | \fB\-h\fR, \fB\-\-help\fR 40 | Show a help message\. 41 | . 42 | .SH "THEME SONG" 43 | The Arcade Fire \- "The Suburbs" 44 | . 45 | .SH "AUTHOR" 46 | Richard Crowley \fIrichard@devstructure\.com\fR 47 | . 48 | .SH "SEE ALSO" 49 | \fBdoubledown\fR was written to make it easier for DevStructure users to use Textmate and other IDEs but it\'s far from the only way to skin the cat\. 50 | . 51 | .P 52 | doubledown\-fsevents(1) watches a directory and relays changes\. It is called by \fBdoubledown\fR\. 53 | . 54 | .P 55 | The source code for \fBdoubledown\fR is available at \fIhttp://github\.com/devstructure/doubledown\fR\. 56 | -------------------------------------------------------------------------------- /bin/doubledown-inotify: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | usage() { 5 | echo "Usage: $(basename $0) [@]:" >&2 6 | [ -n "$1" ] && { 7 | echo " local pathname to sync" >&2 8 | echo " user that will connect to " >&2 9 | echo " remote server" >&2 10 | echo " pathname to push to on " >&2 11 | echo " -h|--help show this help message" >&2 12 | exit 0 13 | } || exit 1 14 | } 15 | 16 | [ "--help" = "$1" ] && usage help 17 | while getopts h NAME 18 | do 19 | case "$NAME" in 20 | h) usage help;; 21 | *) usage;; 22 | esac 23 | done 24 | 25 | LOCAL=$(readlink -f "$1") 26 | [ "$2" != "${2%%@*}" ] && USER="${2%%@*}" 27 | SERVER="${2##*@}"; SERVER="${SERVER%%:*}" 28 | REMOTE="${2##*:}" 29 | [ -z "$LOCAL" ] && usage 30 | [ -z "$USER" ] && usage 31 | [ -z "$SERVER" ] && usage 32 | [ -z "$REMOTE" ] && usage 33 | 34 | # FIXME This does not work. `doubledown-inotify` should wait on all its 35 | # children before succumbing to `SIGINT`. In practice, it's not a huge 36 | # deal because the children run to completion. 37 | trap wait 0 2 38 | 39 | # Let `inotifywait`(1) gather all write events in the directory of interest 40 | # and pass them to a child process for syncing. The child process goes 41 | # into the background so the `inotifywait` output reader can get back to 42 | # business. The shell will `wait`(2) with `WNOHANG` after every command to 43 | # prevent piles of zombie processes from building up. 44 | echo "# [doubledown-fsevents] syncing $LOCAL changes to $SERVER:$REMOTE" >&2 45 | inotifywait -mrq \ 46 | -e modify,attrib,move,move_self,create,delete \ 47 | --format "%e %w%f" "$LOCAL" | while read LINE 48 | do 49 | ( 50 | EVENT="$(echo "$LINE" | cut -d" " -f1)" 51 | PATHNAME="$(echo "$LINE" | cut -d" " -f2-)" 52 | PATHNAME2="${PATHNAME##$LOCAL/}" 53 | case "$EVENT" in 54 | delete|moved_from) 55 | ssh "$USER@$SERVER" rm -d "$PATHNAME2" 2>/dev/null 56 | echo "# [doubledown-inotify] removed $PATHNAME2" >&2;; 57 | *) 58 | echo "# [doubledown-inotify] uploading $PATHNAME2" >&2 59 | rsync -az "$PATHNAME" "$USER@$SERVER:$REMOTE/$PATHNAME2" 60 | echo "# [doubledown-inotify] uploaded $PATHNAME2" >&2;; 61 | esac 62 | ) & 63 | done 64 | -------------------------------------------------------------------------------- /bin/doubledown: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | usage() { 5 | echo "Usage: $(basename $0) [-i ] [@]:" >&2 6 | [ -n "$1" ] && { 7 | echo " local pathname to sync" >&2 8 | echo " user that will connect to " >&2 9 | echo " remote server" >&2 10 | echo " pathname to push to on " >&2 11 | echo " -i non-standard identity (private key)" >&2 12 | echo " -h|--help show this help message" >&2 13 | exit 0 14 | } || exit 1 15 | } 16 | 17 | [ "$1" = "--help" ] && usage help 18 | while getopts i:h NAME 19 | do 20 | case "$NAME" in 21 | i) IDENTITY="$OPTARG";; 22 | h) usage help;; 23 | *) usage;; 24 | esac 25 | done 26 | shift $(($OPTIND - 1)) 27 | 28 | # Resort to Ruby because `readlink`(1) doesn't have `-f` on OS X. 29 | if [ "$(uname -s)" = "Darwin" ] 30 | then 31 | LOCAL=$(ruby -e "puts File.expand_path('$1')") 32 | else 33 | LOCAL=$(readlink -f "$1") 34 | fi 35 | [ "$2" != "${2%%@*}" ] && USER="${2%%@*}" 36 | SERVER="${2##*@}"; SERVER="${SERVER%%:*}" 37 | REMOTE="${2##*:}" 38 | [ -z "$LOCAL" ] && usage 39 | [ -z "$USER" ] && usage 40 | [ -z "$SERVER" ] && usage 41 | [ -z "$REMOTE" ] && usage 42 | 43 | # Grab an existing SSH agent or start one just for `doubledown`, otherwise 44 | # `doubledown-fsevents`(1) and `doubledown-inotify`(1) will be very needy. 45 | if which ssh-agent >/dev/null 46 | then 47 | if [ -S "$SSH_AUTH_SOCK" ] 48 | then 49 | [ -n "$SSH_AGENT_PID" ] && export SSH_AGENT_PID 50 | export SSH_AUTH_SOCK 51 | else 52 | eval $(ssh-agent -s) 53 | trap 'eval $(ssh-agent -sk)' 0 54 | fi 55 | 56 | [ -n "$IDENTITY" ] && ssh-add "$IDENTITY" || \ 57 | [ -n "$(ssh-add -L)" ] || ssh-add || { 58 | echo "# [doubledown] no SSH identity found" >&2 59 | exit 1 60 | } 61 | 62 | else 63 | echo "# [doubledown] ssh-agent not found in your PATH" >&2 64 | exit 1 65 | fi 66 | 67 | # Perform the baseline sync: once, gently, from remote to local and once 68 | # normally from local to remote. 69 | if which rsync >/dev/null 70 | then 71 | echo "# [doubledown] syncing baseline from remote" >&2 72 | rsync -azR --ignore-existing "$USER@$SERVER:$REMOTE/./" "$LOCAL" 73 | echo "# [doubledown] syncing local changes to remote" >&2 74 | rsync -azR "$LOCAL/./" "$USER@$SERVER:$REMOTE" 75 | else 76 | echo "# [doubledown] rsync not found in your PATH" >&2 77 | exit 1 78 | fi 79 | 80 | # That's it. From here on `doubledown-fsevents`(1) or 81 | # `doubledown-inotify`(1) will watch the local directory for changes. 82 | if [ "$(uname -s)" = "Darwin" ] 83 | then 84 | exec doubledown-fsevents "$@" 85 | elif [ "$(uname -s)" = "Linux" ] 86 | then 87 | exec doubledown-inotify "$@" 88 | else 89 | echo "# [doubledown] unsupported operating system" >&2 90 | exit 1 91 | fi 92 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix=/usr/local 2 | 3 | VERSION=0.0.2 4 | BUILD=1 5 | 6 | PACKAGEMAKER=/Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker 7 | 8 | all: 9 | 10 | install: 11 | install -d $(DESTDIR)$(prefix)/bin 12 | install bin/doubledown $(DESTDIR)$(prefix)/bin/ 13 | install -d $(DESTDIR)$(prefix)/share/man/man1 14 | install -m644 man/man1/doubledown.1 \ 15 | $(DESTDIR)$(prefix)/share/man/man1/ 16 | make install-$(shell uname -s) 17 | 18 | install-Darwin: 19 | install bin/doubledown-fsevents $(DESTDIR)$(prefix)/bin/ 20 | install -m644 man/man1/doubledown-fsevents.1 \ 21 | $(DESTDIR)$(prefix)/share/man/man1/ 22 | 23 | install-Linux: 24 | install bin/doubledown-inotify $(DESTDIR)$(prefix)/bin/ 25 | install -m644 man/man1/doubledown-inotify.1 \ 26 | $(DESTDIR)$(prefix)/share/man/man1/ 27 | 28 | uninstall: 29 | rm -f \ 30 | $(DESTDIR)$(prefix)/bin/doubledown \ 31 | $(DESTDIR)$(prefix)/bin/doubledown-fsevents \ 32 | $(DESTDIR)$(prefix)/bin/doubledown-inotify \ 33 | $(DESTDIR)$(prefix)/share/man/man1/doubledown.1 \ 34 | $(DESTDIR)$(prefix)/share/man/man1/doubledown-fsevents.1 \ 35 | $(DESTDIR)$(prefix)/share/man/man1/doubledown-inotify.1 36 | rmdir -p --ignore-fail-on-non-empty \ 37 | $(DESTDIR)$(prefix)/bin \ 38 | $(DESTDIR)$(prefix)/share/man/man1 39 | 40 | package: 41 | make package-$(shell uname -s) 42 | 43 | package-Darwin: 44 | sudo rm -rf package 45 | sudo mkdir package 46 | sudo make install DESTDIR=package prefix=/usr 47 | sudo $(PACKAGEMAKER) -r package \ 48 | -i com.devstructure.doubledown \ 49 | --version $(VERSION) -o doubledown.pkg 50 | tar czf doubledown-$(VERSION).tar.gz doubledown.pkg 51 | sudo rm -rf package doubledown.pkg 52 | 53 | package-Linux: deb 54 | 55 | deb: 56 | [ "$$(whoami)" = "root" ] || false 57 | m4 -D__VERSION__=$(VERSION)-$(BUILD) control.m4 >control 58 | debra create debian control 59 | make install DESTDIR=debian prefix=/usr 60 | chown -R root:root debian 61 | debra build debian doubledown_$(VERSION)-$(BUILD)_all.deb 62 | debra destroy debian 63 | 64 | deploy: 65 | scp -i ~/production.pem doubledown_$(VERSION)-$(BUILD)_all.deb ubuntu@packages.devstructure.com: 66 | ssh -i ~/production.pem -t ubuntu@packages.devstructure.com "sudo freight add doubledown_$(VERSION)-$(BUILD)_all.deb apt/lenny apt/squeeze apt/lucid apt/maverick apt/natty && rm doubledown_$(VERSION)-$(BUILD)_all.deb && sudo freight cache apt/lenny apt/squeeze apt/lucid apt/maverick apt/natty" 67 | 68 | man: 69 | find man -name \*.ronn | xargs -n1 ronn \ 70 | --manual=Doubledown --organization=DevStructure --style=toc 71 | 72 | gh-pages: man 73 | mkdir -p gh-pages 74 | find man -name \*.html | xargs -I__ mv __ gh-pages/ 75 | git checkout -q gh-pages 76 | mv gh-pages/* ./ 77 | git add . 78 | git commit -m "Rebuilt manual." 79 | git push origin gh-pages 80 | git checkout -q master 81 | rmdir gh-pages 82 | 83 | .PHONY: all install install-Darwin install-Linux uninstall package package-Darwin package-Linux deb man docs gh-pages 84 | -------------------------------------------------------------------------------- /bin/doubledown-fsevents: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'optparse' 4 | require 'osx/foundation' 5 | 6 | OSX.require_framework "/System/Library/Frameworks/CoreServices.framework#{ 7 | }/Frameworks/CarbonCore.framework" 8 | 9 | class Doubledown 10 | 11 | def initialize(local, remote) 12 | 13 | # Decide on the local path. 14 | Dir.mkdir(local) unless File.directory?(local) 15 | @local = File.expand_path(local) 16 | 17 | # Parse the server and remote path. Decide on the remote username. 18 | raise unless remote =~ /^(?:([^@]+)@)?([^:]+):(.+)$/ 19 | @user, @server, @remote = $1, $2, $3 20 | @user ||= ENV["USER"] 21 | 22 | end 23 | 24 | def run 25 | $stderr.puts "# [doubledown-fsevents] syncing #{@local 26 | } changes to #{@server}:#{@remote}" 27 | 28 | # Begin incremental syncing from local to remote. 29 | stream = OSX.FSEventStreamCreate( 30 | OSX::KCFAllocatorDefault, 31 | lambda { |stream, context, count, paths, flags, events| 32 | 33 | paths.regard_as("*") 34 | count.times do |i| 35 | dirname = paths[i].gsub(%r, "") 36 | deleted_files dirname 37 | modified_files dirname 38 | end 39 | recache 40 | 41 | # Keep the process tree clean. 42 | begin 43 | while pid = Process.wait(-1); end 44 | rescue; end 45 | 46 | }, 47 | nil, 48 | [@local], 49 | OSX::KFSEventStreamEventIdSinceNow, 50 | 0.1, 51 | 0 52 | ) or raise 53 | OSX.FSEventStreamScheduleWithRunLoop( 54 | stream, 55 | OSX.CFRunLoopGetCurrent, 56 | OSX::KCFRunLoopDefaultMode 57 | ) 58 | OSX.FSEventStreamStart(stream) or raise 59 | recache 60 | 61 | # This call never returns except by an exception. 62 | begin 63 | OSX.CFRunLoopRun 64 | rescue Interrupt 65 | end 66 | 67 | end 68 | 69 | private 70 | 71 | def deleted_file(pathname) 72 | pathname2 = "#{@remote}#{pathname.sub(@local, "")}" 73 | ssh("rm -d #{pathname2.inspect} 2>/dev/null", "removed #{pathname2}") 74 | rescue => e 75 | $stderr.puts e.inspect 76 | $stderr.puts e.backtrace 77 | raise e 78 | end 79 | 80 | def deleted_files(dirname) 81 | ((@cache[dirname] || {}).keys - Dir.entries(dirname)).each do |basename| 82 | deleted_file File.join(dirname, basename) 83 | end 84 | end 85 | 86 | # Run a command in the background. This actually forks twice, leaving the 87 | # intermediate process an opportunity to print a message. 88 | def fork_exec(argv, message) 89 | fork do 90 | if system(*argv) 91 | $stderr.puts "# [doubledown-fsevents] #{message}" 92 | end 93 | end 94 | end 95 | 96 | def modified_file(pathname) 97 | pathname2 = "#{@remote}#{pathname.sub(@local, "")}" 98 | $stderr.puts "# [doubledown-fsevents] uploading #{pathname2}" 99 | fork_exec([ 100 | "rsync", 101 | "-az", 102 | pathname, 103 | "#{@user}@#{@server}:#{pathname2}", 104 | ], "uploaded #{pathname2}") 105 | rescue => e 106 | $stderr.puts e.inspect 107 | $stderr.puts e.backtrace 108 | raise e 109 | end 110 | 111 | def modified_files(dirname) 112 | Dir.foreach dirname do |basename| 113 | next if "." == basename || ".." == basename 114 | pathname = File.join(dirname, basename) 115 | if cached_stat = (@cache[dirname] || {})[basename] 116 | stat = File.lstat(pathname) 117 | if cached_stat.mtime != stat.mtime || cached_stat.size != stat.size 118 | modified_file pathname 119 | end 120 | else 121 | modified_file pathname 122 | end 123 | end 124 | end 125 | 126 | # Keep a cache of `File::Stat` objects because the FSEvents API is not 127 | # as awesome as `inotify`(7) on Linux. 128 | def recache(dirname=@local) 129 | @cache ||= {} 130 | @cache[dirname] = {} 131 | Dir.foreach dirname do |basename| 132 | pathname = File.join(dirname, basename) 133 | stat = File.lstat(pathname) 134 | @cache[dirname][basename] = stat 135 | if stat.symlink? 136 | elsif stat.directory? && "." != basename && ".." != basename 137 | recache pathname 138 | end 139 | end 140 | end 141 | 142 | # Run a command remotely via SSH. Avoid single quotes in the command. 143 | def ssh(command, message) 144 | fork_exec([ 145 | "ssh", 146 | "#{@user}@#{@server}", 147 | "/bin/sh -c '#{command}'", 148 | ], message) 149 | end 150 | 151 | end 152 | 153 | options = {} 154 | OptionParser.new do |parser| 155 | parser.banner = "Usage: [@]:" 156 | parser.on("-h", "--help", "show this help message") { options[:help] = true } 157 | end.parse! 158 | 159 | trap("EXIT") { Process.waitall } 160 | 161 | Doubledown.new(ARGV[0], ARGV[1]).run 162 | --------------------------------------------------------------------------------