[from now] 2014/08/30 08:43:50

diff --git a/cmd/copybackup/copybackup.go b/cmd/copybackup/copybackup.go
index 1664f17..978d312 100644
--- a/cmd/copybackup/copybackup.go
+++ b/cmd/copybackup/copybackup.go
@@ -13,9 +13,9 @@ import (
 )

 type Options struct {
-	Generation int    `short:"g" long:"generation" description:"バックアップする世代。 (デフォルト [5])" default:"5"`
-	BackupDst  string `short:"b" long:"backup" description:"バックアップを保存する先。 (デフォルト [_old])" default:"_old"`
-	Sleep      int    `short:"s" long:"sleep" description:"バックアップ間隔。 (秒 [デフォルト 5分])" default:"60*5"`
+	Generation int    `short:"g" long:"generation" description:"バックアップする世代。" default:"5"`
+	BackupDst  string `short:"b" long:"backup" description:"バックアップを保存する先。" default:"_old"`
+	Sleep      int    `short:"s" long:"sleep" description:"バックアップ間隔。" default:"60*5"`
 	Csv        string `short:"c" long:"csv" description:"設定ファイルから読み込み。(csvファイル)"`
 }

@@ -26,7 +26,7 @@ func main() {
 	var wg sync.WaitGroup
 	var cg *cb.CopyGroup

-	opts := &Options{}
+	opts := new(Options)
 	parser := flags.NewParser(opts, flags.Default)
 	args, e := parser.Parse()
 	core.FailOnError(e)
@@ -37,7 +37,7 @@ func main() {
 	}

 	for _, arg := range args {
-		if f, _ := os.Stat(arg); f.IsDir() {
+		if f, e := os.Stat(arg); f.IsDir() {
 			files, e := ioutil.ReadDir(arg)
 			core.FailOnError(e)
 			for _, f := range files {
@@ -50,7 +50,8 @@ func main() {
 					}()
 				}
 			}
-
+		} else if e != nil {
+			core.FailOnError(e)
 		} else {
 			cg = cb.NewCopyGroup(arg, opts.BackupDst, opts.Generation, opts.Sleep)
 			wg.Add(1)
diff --git a/copybackup.go b/copybackup.go
index 5e6fcb1..5dea195 100644
--- a/copybackup.go
+++ b/copybackup.go
@@ -3,8 +3,10 @@ package copybackup
 import ( // {{{
 	"fmt"
 	"io"
+	"io/ioutil"
 	"os"
 	"path/filepath"
+	"regexp"
 	"strings"
 	"time"

@@ -24,7 +26,6 @@ func makeDstPath(src, bkpath string) (string, error) { // {{{

 	f, e := os.Stat(src)
 	if e == nil && f.IsDir() {
-		// core.Logger.Warnf("%s is Directory !", src)
 		return "", fmt.Errorf("%s is Directory !", src)
 	}
 	parent, base := filepath.Split(src)
@@ -51,8 +52,114 @@ func (cg *CopyGroup) Backup() error { // {{{
 	dstParent := filepath.Dir(cg.dst)
 	e = os.MkdirAll(dstParent, os.ModePerm)
 	core.FailOnError(e)
+	latestFile, e := cg.getLatestFile()
+	core.FailOnError(e)
+	if latestFile != "" {
+		l, _ := os.Stat(latestFile)
+		s, _ := os.Stat(cg.src)
+		core.Logger.Debug("cg.src ModTime = ", s.ModTime().Format(time.StampMilli))
+		core.Logger.Debug("latestFile ModTime = ", l.ModTime().Format(time.StampMilli))
+		if l.ModTime().Equal(s.ModTime()) {
+			core.Logger.Debugf("[%s] is same as [%s]", cg.src, latestFile)
+			return nil
+		}
+	}
 	core.Logger.Infof("%s -> %s", cg.src, cg.dst)
-	return cp(cg.dst, cg.src)
+	e = cp(cg.dst, cg.src)
+	cg.deleteOldFile()
+	return e
+} // }}}
+
+func (cg *CopyGroup) deleteOldFile() { // {{{
+	if cg.generation != -1 {
+		for {
+			if cg.countMatchFiles() > cg.generation {
+				oldestFile, e := cg.getOldestFile()
+				core.FailOnError(e)
+				os.Remove(oldestFile)
+			} else {
+				break
+			}
+		}
+	}
+} // }}}
+
+func (cg *CopyGroup) countMatchFiles() int { // {{{
+	var count int
+
+	basename := core.GetBaseName(cg.src)
+	ext := filepath.Ext(cg.src)
+	matchs := `^` + basename + `_[0-9]{8}-[0-9]{6}` + ext + `$`
+
+	files, e := ioutil.ReadDir(filepath.Dir(cg.dst))
+	core.FailOnError(e)
+
+	for _, file := range files {
+		m, e := regexp.MatchString(matchs, file.Name())
+		core.FailOnError(e)
+		if m {
+			count++
+		}
+	}
+	return count
+} // }}}
+
+func (cg *CopyGroup) getLatestFile() (string, error) { // {{{
+	var latestFile os.FileInfo
+	var e error
+
+	basename := core.GetBaseName(cg.src)
+	ext := filepath.Ext(cg.src)
+	matchs := `^` + basename + `_[0-9]{8}-[0-9]{6}` + ext + `$`
+
+	dstDir := filepath.Dir(cg.dst)
+	files, e := ioutil.ReadDir(dstDir)
+	core.FailOnError(e)
+
+	if len(files) == 0 {
+		return "", nil
+	}
+
+	latestFile = files[0]
+	for _, file := range files {
+		m, e := regexp.MatchString(matchs, file.Name())
+		core.FailOnError(e)
+		if m {
+			if latestFile.ModTime().Before(file.ModTime()) {
+				latestFile = file
+			}
+		}
+	}
+	return filepath.Join(dstDir, latestFile.Name()), e
+} // }}}
+
+func (cg *CopyGroup) getOldestFile() (string, error) { // {{{
+	var oldestFile os.FileInfo
+	var e error
+
+	basename := core.GetBaseName(cg.src)
+	ext := filepath.Ext(cg.src)
+	matchs := `^` + basename + `_[0-9]{8}-[0-9]{6}` + ext + `$`
+
+	dstDir := filepath.Dir(cg.dst)
+	files, e := ioutil.ReadDir(dstDir)
+	core.FailOnError(e)
+
+	if len(files) == 0 {
+		return "", nil
+	}
+
+	oldestFile = files[0]
+	for _, file := range files {
+		m, e := regexp.MatchString(matchs, file.Name())
+		core.FailOnError(e)
+		if m {
+			if oldestFile.ModTime().After(file.ModTime()) {
+				oldestFile = file
+			}
+		}
+	}
+	return filepath.Join(dstDir, oldestFile.Name()), e
 } // }}}

 func cp(dst, src string) error { // {{{
diff --git a/copybackup_test.go b/copybackup_test.go
index 388379d..b0a729c 100644
--- a/copybackup_test.go
+++ b/copybackup_test.go
@@ -5,15 +5,48 @@ import (
 	"path/filepath"
 	"regexp"
 	"testing"
+	"time"

 	"github.com/umisama/golog"
 	core "github.com/yukimemi/gocore"
 )

-func TestMakeDstPath1(t *testing.T) { // {{{
+const THIS_FILE_NAME string = "copybackup_test.go"
+const GENERATION = 3
+const TEST_FILES_COUNT = 6
+
+var cg *CopyGroup
+var files []string
+var oldestFileExpected string
+var latestFileExpected string
+
+func init() { // {{{
 	core.Logger, _ = log.NewLogger(os.Stdout, log.TIME_FORMAT_SEC, log.LOG_FORMAT_POWERFUL, log.LogLevel_Debug)
 	pwd, _ := os.Getwd()
-	src := filepath.Join(pwd, "copybackup_test.go")
+	files = make([]string, 0)
+	cg = NewCopyGroup(filepath.Join(pwd, THIS_FILE_NAME), "_old", GENERATION, 60)
+	dstDir := filepath.Dir(cg.dst)
+	if _, e := os.Stat(dstDir); e == nil {
+		os.RemoveAll(dstDir)
+	}
+	os.MkdirAll(dstDir, os.ModePerm)
+
+	for i := 0; i < TEST_FILES_COUNT; i++ {
+		cg = NewCopyGroup(filepath.Join(pwd, THIS_FILE_NAME), "_old", GENERATION, 60)
+		cp(cg.dst, cg.src)
+		if i == 0 {
+			oldestFileExpected = cg.dst
+		} else if i == TEST_FILES_COUNT-1 {
+			latestFileExpected = cg.dst
+		}
+		files = append(files, cg.dst)
+		time.Sleep(time.Second)
+	}
+} // }}}
+
+func TestMakeDstPath1(t *testing.T) { // {{{
+	pwd, _ := os.Getwd()
+	src := filepath.Join(pwd, THIS_FILE_NAME)
 	bkpath := "_old"

 	expected := "^" + pwd + `/_old/copybackup_test_[0-9]{8}-[0-9]{6}\.go$`
@@ -23,16 +56,15 @@ func TestMakeDstPath1(t *testing.T) { // {{{
 	}

 	if m, e := regexp.MatchString(expected, dst); m && e == nil {
-		core.Logger.Infof("dst = [%s]", dst)
+		// core.Logger.Infof("dst = [%s]", dst)
 	} else {
 		t.Errorf("expected = [%s] but dst is = [%s]", expected, dst)
 	}
 } // }}}

 func TestMakeDstPath2(t *testing.T) { // {{{
-	core.Logger, _ = log.NewLogger(os.Stdout, log.TIME_FORMAT_SEC, log.LOG_FORMAT_POWERFUL, log.LogLevel_Debug)
 	pwd, _ := os.Getwd()
-	src := filepath.Join(pwd, "copybackup_test.go")
+	src := filepath.Join(pwd, THIS_FILE_NAME)
 	bkpath := "/Users/yukimemi/backup"

 	expected := `^/Users/yukimemi/backup/copybackup_test_[0-9]{8}-[0-9]{6}\.go$`
@@ -42,16 +74,63 @@ func TestMakeDstPath2(t *testing.T) { // {{{
 	}

 	if m, e := regexp.MatchString(expected, dst); m && e == nil {
-		core.Logger.Infof("dst = [%s]", dst)
+		// core.Logger.Infof("dst = [%s]", dst)
 	} else {
 		t.Errorf("expected = [%s] but dst is = [%s]", expected, dst)
 	}
 } // }}}

-func TestCopyGroup(t *testing.T) {
-	core.Logger, _ = log.NewLogger(os.Stdout, log.TIME_FORMAT_SEC, log.LOG_FORMAT_POWERFUL, log.LogLevel_Debug)
+func TestCountMatchFiles(t *testing.T) { // {{{
+	count := cg.countMatchFiles()
+	expected := TEST_FILES_COUNT
+
+	if count != expected {
+		t.Errorf("expected = [%d] but count = [%d]", expected, count)
+	}
+} // }}}
+
+func TestGetOldestFile(t *testing.T) { // {{{
+	oldestFile, _ := cg.getOldestFile()
+	if oldestFile != oldestFileExpected {
+		t.Errorf("expected = [%s] but oldestFile = [%s]", oldestFileExpected, oldestFile)
+	}
+} // }}}
+
+func TestGetLatestFile(t *testing.T) { // {{{
+	latestFile, _ := cg.getLatestFile()
+	if latestFile != latestFileExpected {
+		t.Errorf("expected = [%s] but latestFile = [%s]", latestFileExpected, latestFile)
+	}
+} // }}}
+
+func TestDeleteOldFile1(t *testing.T) { // {{{
 	pwd, _ := os.Getwd()
-	src := filepath.Join(pwd, "copybackup_test.go")
-	cg := NewCopyGroup(src, "_old", 3, 60*10)
-	core.Logger.Infof("cg = [%s]", cg)
+	cg := NewCopyGroup(filepath.Join(pwd, THIS_FILE_NAME), "_old", -1, 60)
+	cg.deleteOldFile()
+	count := cg.countMatchFiles()
+	expected := TEST_FILES_COUNT
+
+	if count != expected {
+		t.Errorf("expected = [%d] but count = [%d]", expected, count)
+	}
+} // }}}
+
+func TestDeleteOldFile2(t *testing.T) { // {{{
+	cg.deleteOldFile()
+	count := cg.countMatchFiles()
+	expected := GENERATION
+
+	if count != expected {
+		t.Errorf("expected = [%d] but count = [%d]", expected, count)
+	}
+} // }}}
+
+func TestBackup(t *testing.T) {
+	cg.Backup()
+	count := cg.countMatchFiles()
+	expected := GENERATION
+
+	if count != expected {
+		t.Errorf("expected = [%d] but count = [%d]", expected, count)
+	}
 }
diff --git a/wercker.yml b/wercker.yml
new file mode 100644
index 0000000..3dcb185
--- /dev/null
+++ b/wercker.yml
@@ -0,0 +1,54 @@
+box: lestrrat/peco-build
+build:
+  steps:
+    - setup-go-workspace
+    - script:
+        name: go get
+        code: |
+          go version
+          godep restore
+    - script:
+        name: go test
+        code: |
+          godep go test ./...
+    - script:
+        name: goxc build & archive
+        code: |
+          GODEPPATH=`godep path`
+          export GOPATH="$GODEPPATH:$GOPATH"
+          goxc -tasks='xc archive' -bc 'linux windows darwin' -d $WERCKER_OUTPUT_DIR/ -resources-include='README*' -resources-include='Changes'
+    - script:
+        name: output release tag
+        code: |
+          git describe --tags --exact --match 'v*' > $WERCKER_OUTPUT_DIR/.release_tag || true
+deploy:
+  steps:
+    - script:
+        name: restore release tag
+        code: |
+          export RELEASE_TAG=$(cat .release_tag)
+    - motemen/github-create-release:
+        token: $GITHUB_TOKEN
+        tag: $RELEASE_TAG
+    - wercker/github-upload-asset:
+        token: $GITHUB_TOKEN
+        file: snapshot/peco_linux_386.tar.gz
+    - wercker/github-upload-asset:
+        token: $GITHUB_TOKEN
+        file: snapshot/peco_linux_amd64.tar.gz
+    - wercker/github-upload-asset:
+        token: $GITHUB_TOKEN
+        file: snapshot/peco_linux_arm.tar.gz
+    - wercker/github-upload-asset:
+        token: $GITHUB_TOKEN
+        file: snapshot/peco_darwin_386.zip
+    - wercker/github-upload-asset:
+        token: $GITHUB_TOKEN
+        file: snapshot/peco_darwin_amd64.zip
+    - wercker/github-upload-asset:
+        token: $GITHUB_TOKEN
+        file: snapshot/peco_windows_386.zip
+    - wercker/github-upload-asset:
+        token: $GITHUB_TOKEN
+        file: snapshot/peco_windows_amd64.zip
+    - lestrrat/peco-update-homebrew
[from now] 2014/08/30 08:56:45

diff --git a/wercker.yml b/wercker.yml
index 3dcb185..4caaa94 100644
--- a/wercker.yml
+++ b/wercker.yml
@@ -32,23 +32,22 @@ deploy:
         tag: $RELEASE_TAG
     - wercker/github-upload-asset:
         token: $GITHUB_TOKEN
-        file: snapshot/peco_linux_386.tar.gz
+        file: snapshot/copybackup_linux_386.tar.gz
     - wercker/github-upload-asset:
         token: $GITHUB_TOKEN
-        file: snapshot/peco_linux_amd64.tar.gz
+        file: snapshot/copybackup_linux_amd64.tar.gz
     - wercker/github-upload-asset:
         token: $GITHUB_TOKEN
-        file: snapshot/peco_linux_arm.tar.gz
+        file: snapshot/copybackup_linux_arm.tar.gz
     - wercker/github-upload-asset:
         token: $GITHUB_TOKEN
-        file: snapshot/peco_darwin_386.zip
+        file: snapshot/copybackup_darwin_386.zip
     - wercker/github-upload-asset:
         token: $GITHUB_TOKEN
-        file: snapshot/peco_darwin_amd64.zip
+        file: snapshot/copybackup_darwin_amd64.zip
     - wercker/github-upload-asset:
         token: $GITHUB_TOKEN
-        file: snapshot/peco_windows_386.zip
+        file: snapshot/copybackup_windows_386.zip
     - wercker/github-upload-asset:
         token: $GITHUB_TOKEN
-        file: snapshot/peco_windows_amd64.zip
-    - lestrrat/peco-update-homebrew
+        file: snapshot/copybackup_windows_amd64.zip