Browse Source

Add external versioner (ref #573)

Alexander Graf 10 years ago
parent
commit
d3f1eaf1a3

+ 15 - 0
gui/index.html

@@ -237,6 +237,7 @@
                       <td class="text-right" ng-switch="folder.Versioning.Type">
                         <span ng-switch-when="staggered" translate>Staggered File Versioning</span>
                         <span ng-switch-when="simple" translate>Simple File Versioning</span>
+                        <span ng-switch-when="external" translate>External File Versioning</span>
                       </td>
                     </tr>
                     <tr>
@@ -620,6 +621,11 @@
                       <input type="radio" ng-model="currentFolder.fileVersioningSelector" value="staggered"> <span translate>Staggered File Versioning</span>
                     </label>
                   </div>
+                  <div class="radio">
+                    <label>
+                      <input type="radio" ng-model="currentFolder.fileVersioningSelector" value="external"> <span translate>External File Versioning</span>
+                    </label>
+                  </div>
                 </div>
                 <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
                   <p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.</p>
@@ -646,6 +652,15 @@
                   <input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath"></input>
                   <p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions folder in the folder).</p>
                 </div>
+                <div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
+                  <p translate class="help-block">A external command handles the versioning. It has to remove the file from the synced folder.</p>
+                  <label translate for="externalCommand">Command</label>
+                  <input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required></input>
+                  <p class="help-block">
+                    <span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">The first command line parameter is the folder path and the second parameter is the relative path in the folder.</span>
+                    <span translate ng-if="folderEditor.externalCommand.$error.required  && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
+                  </p>
+                </div>
               </div>
             </div>
 

+ 17 - 1
gui/scripts/syncthing/core/controllers/syncthingController.js

@@ -904,6 +904,10 @@ angular.module('syncthing.core')
                 $scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
                 $scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
                 $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
+            } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
+                $scope.currentFolder.externalFileVersioning = true;
+                $scope.currentFolder.fileVersioningSelector = "external";
+                $scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
             } else {
                 $scope.currentFolder.fileVersioningSelector = "none";
             }
@@ -917,7 +921,8 @@ angular.module('syncthing.core')
             if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
                 $scope.currentFolder.staggeredMaxAge = 365;
             }
-
+            $scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
+            
             $scope.editingExisting = true;
             $scope.folderEditor.$setPristine();
             $('#editFolder').modal();
@@ -933,6 +938,7 @@ angular.module('syncthing.core')
             $scope.currentFolder.staggeredMaxAge = 365;
             $scope.currentFolder.staggeredCleanInterval = 3600;
             $scope.currentFolder.staggeredVersionsPath = "";
+            $scope.currentFolder.externalCommand = "";
             $scope.editingExisting = false;
             $scope.folderEditor.$setPristine();
             $('#editFolder').modal();
@@ -952,6 +958,7 @@ angular.module('syncthing.core')
             $scope.currentFolder.staggeredMaxAge = 365;
             $scope.currentFolder.staggeredCleanInterval = 3600;
             $scope.currentFolder.staggeredVersionsPath = "";
+            $scope.currentFolder.externalCommand = "";
             $scope.editingExisting = false;
             $scope.folderEditor.$setPristine();
             $('#editFolder').modal();
@@ -1005,6 +1012,15 @@ angular.module('syncthing.core')
                 delete folderCfg.staggeredCleanInterval;
                 delete folderCfg.staggeredVersionsPath;
 
+            } else if (folderCfg.fileVersioningSelector === "external") {
+                folderCfg.versioning = {
+                    'Type': 'external',
+                    'Params': {
+                        'command': '' + folderCfg.externalCommand
+                    }
+                };
+                delete folderCfg.externalFileVersioning;
+                delete folderCfg.externalCommand;
             } else {
                 delete folderCfg.versioning;
             }

File diff suppressed because it is too large
+ 0 - 0
internal/auto/gui.files.go


+ 89 - 0
internal/versioner/external.go

@@ -0,0 +1,89 @@
+// Copyright (C) 2015 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package versioner
+
+import (
+	"errors"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+func init() {
+	// Register the constructor for this type of versioner with the name "external"
+	Factories["external"] = NewExternal
+}
+
+// The type holds our configuration
+type External struct {
+	command    string
+	folderPath string
+}
+
+// The constructor function takes a map of parameters and creates the type.
+func NewExternal(folderID, folderPath string, params map[string]string) Versioner {
+	command := params["command"]
+
+	s := External{
+		command:    command,
+		folderPath: folderPath,
+	}
+
+	if debug {
+		l.Debugf("instantiated %#v", s)
+	}
+	return s
+}
+
+// Move away the named file to a version archive. If this function returns
+// nil, the named file does not exist any more (has been archived).
+func (v External) Archive(filePath string) error {
+	_, err := os.Lstat(filePath)
+	if os.IsNotExist(err) {
+		if debug {
+			l.Debugln("not archiving nonexistent file", filePath)
+		}
+		return nil
+	} else if err != nil {
+		return err
+	}
+
+	if debug {
+		l.Debugln("archiving", filePath)
+	}
+
+	inFolderPath, err := filepath.Rel(v.folderPath, filePath)
+	if err != nil {
+		return err
+	}
+
+	if v.command == "" {
+		return errors.New("Versioner: command is empty, please enter a valid command")
+	}
+
+	cmd := exec.Command(v.command, v.folderPath, inFolderPath)
+	env := os.Environ()
+	// filter STGUIAUTH and STGUIAPIKEY from environment variables
+	filteredEnv := []string{}
+	for _, x := range env {
+		if !strings.HasPrefix(x, "STGUIAUTH=") && !strings.HasPrefix(x, "STGUIAPIKEY=") {
+			filteredEnv = append(filteredEnv, x)
+		}
+	}
+	cmd.Env = filteredEnv
+	err = cmd.Run()
+	if err != nil {
+		return err
+	}
+
+	// return error if the file was not removed
+	if _, err = os.Lstat(filePath); os.IsNotExist(err) {
+		return nil
+	}
+	return errors.New("Versioner: file was not removed by external script")
+}

+ 5 - 6
internal/versioner/simple.go

@@ -47,13 +47,12 @@ func NewSimple(folderID, folderPath string, params map[string]string) Versioner
 // nil, the named file does not exist any more (has been archived).
 func (v Simple) Archive(filePath string) error {
 	fileInfo, err := os.Lstat(filePath)
-	if err != nil {
-		if os.IsNotExist(err) {
-			if debug {
-				l.Debugln("not archiving nonexistent file", filePath)
-			}
-			return nil
+	if os.IsNotExist(err) {
+		if debug {
+			l.Debugln("not archiving nonexistent file", filePath)
 		}
+		return nil
+	} else if err != nil {
 		return err
 	}
 

+ 6 - 6
internal/versioner/staggered.go

@@ -281,13 +281,13 @@ func (v Staggered) Archive(filePath string) error {
 	v.mutex.Lock()
 	defer v.mutex.Unlock()
 
-	if _, err := os.Lstat(filePath); err != nil {
-		if os.IsNotExist(err) {
-			if debug {
-				l.Debugln("not archiving nonexistent file", filePath)
-			}
-			return nil
+	_, err := os.Lstat(filePath)
+	if os.IsNotExist(err) {
+		if debug {
+			l.Debugln("not archiving nonexistent file", filePath)
 		}
+		return nil
+	} else if err != nil {
 		return err
 	}
 

Some files were not shown because too many files changed in this diff