384 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			384 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"crypto/md5"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"go/format"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"runtime"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/UnnoTed/fileb0x/compression"
 | |
| 	"github.com/UnnoTed/fileb0x/config"
 | |
| 	"github.com/UnnoTed/fileb0x/custom"
 | |
| 	"github.com/UnnoTed/fileb0x/dir"
 | |
| 	"github.com/UnnoTed/fileb0x/file"
 | |
| 	"github.com/UnnoTed/fileb0x/template"
 | |
| 	"github.com/UnnoTed/fileb0x/updater"
 | |
| 	"github.com/UnnoTed/fileb0x/utils"
 | |
| 
 | |
| 	// just to install automatically
 | |
| 	_ "github.com/labstack/echo"
 | |
| 	_ "golang.org/x/net/webdav"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	err     error
 | |
| 	cfg     *config.Config
 | |
| 	files   = make(map[string]*file.File)
 | |
| 	dirs    = new(dir.Dir)
 | |
| 	cfgPath string
 | |
| 
 | |
| 	fUpdate   string
 | |
| 	startTime = time.Now()
 | |
| 
 | |
| 	hashStart = []byte("// modification hash(")
 | |
| 	hashEnd   = []byte(")")
 | |
| 
 | |
| 	modTimeStart = []byte("// modified(")
 | |
| 	modTimeEnd   = []byte(")")
 | |
| )
 | |
| 
 | |
| func main() {
 | |
| 	runtime.GOMAXPROCS(runtime.NumCPU())
 | |
| 
 | |
| 	// check for updates
 | |
| 	flag.StringVar(&fUpdate, "update", "", "-update=http(s)://host:port - default port: 8041")
 | |
| 	flag.Parse()
 | |
| 	var (
 | |
| 		update = fUpdate != ""
 | |
| 		up     *updater.Updater
 | |
| 	)
 | |
| 
 | |
| 	// create config and try to get b0x file from args
 | |
| 	f := new(config.File)
 | |
| 	err = f.FromArg(true)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// load b0x file's config
 | |
| 	cfg, err = f.Load()
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	err = cfg.Defaults()
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	cfgPath = f.FilePath
 | |
| 
 | |
| 	if err := cfg.Updater.CheckInfo(); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	cfg.Updater.IsUpdating = update
 | |
| 
 | |
| 	// creates a config that can be inserTed into custom
 | |
| 	// without causing a import cycle
 | |
| 	sharedConfig := new(custom.SharedConfig)
 | |
| 	sharedConfig.Output = cfg.Output
 | |
| 	sharedConfig.Updater = cfg.Updater
 | |
| 	sharedConfig.Compression = compression.NewGzip()
 | |
| 	sharedConfig.Compression.Options = cfg.Compression
 | |
| 
 | |
| 	// loop through b0x's [custom] objects
 | |
| 	for _, c := range cfg.Custom {
 | |
| 		err = c.Parse(&files, &dirs, sharedConfig)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// builds remap's list
 | |
| 	var (
 | |
| 		remap    string
 | |
| 		modHash  string
 | |
| 		mods     []string
 | |
| 		lastHash string
 | |
| 	)
 | |
| 
 | |
| 	for _, f := range files {
 | |
| 		remap += f.GetRemap()
 | |
| 		mods = append(mods, f.Modified)
 | |
| 	}
 | |
| 
 | |
| 	// sorts modification time list and create a md5 of it
 | |
| 	sort.Strings(mods)
 | |
| 	modHash = stringMD5Hex(strings.Join(mods, "")) + "." + stringMD5Hex(string(f.Data))
 | |
| 	exists := fileExists(cfg.Dest + cfg.Output)
 | |
| 
 | |
| 	if exists {
 | |
| 		// gets the modification hash from the main b0x file
 | |
| 		lastHash, err = getModification(cfg.Dest+cfg.Output, hashStart, hashEnd)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !exists || lastHash != modHash {
 | |
| 		// create files template and exec it
 | |
| 		t := new(template.Template)
 | |
| 		t.Set("files")
 | |
| 		t.Variables = struct {
 | |
| 			ConfigFile       string
 | |
| 			Now              string
 | |
| 			Pkg              string
 | |
| 			Files            map[string]*file.File
 | |
| 			Tags             string
 | |
| 			Spread           bool
 | |
| 			Remap            string
 | |
| 			DirList          []string
 | |
| 			Compression      *compression.Options
 | |
| 			Debug            bool
 | |
| 			Updater          updater.Config
 | |
| 			ModificationHash string
 | |
| 		}{
 | |
| 			ConfigFile:       filepath.Base(cfgPath),
 | |
| 			Now:              time.Now().String(),
 | |
| 			Pkg:              cfg.Pkg,
 | |
| 			Files:            files,
 | |
| 			Tags:             cfg.Tags,
 | |
| 			Remap:            remap,
 | |
| 			Spread:           cfg.Spread,
 | |
| 			DirList:          dirs.Clean(),
 | |
| 			Compression:      cfg.Compression,
 | |
| 			Debug:            cfg.Debug,
 | |
| 			Updater:          cfg.Updater,
 | |
| 			ModificationHash: modHash,
 | |
| 		}
 | |
| 
 | |
| 		tmpl, err := t.Exec()
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 
 | |
| 		if err := os.MkdirAll(cfg.Dest, 0770); err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 
 | |
| 		// gofmt
 | |
| 		if cfg.Fmt {
 | |
| 			tmpl, err = format.Source(tmpl)
 | |
| 			if err != nil {
 | |
| 				panic(err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// write final execuTed template into the destination file
 | |
| 		err = ioutil.WriteFile(cfg.Dest+cfg.Output, tmpl, 0640)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// write spread files
 | |
| 	var (
 | |
| 		finalList   []string
 | |
| 		changedList []string
 | |
| 	)
 | |
| 	if cfg.Spread {
 | |
| 		a := strings.Split(path.Dir(cfg.Dest), "/")
 | |
| 		dirName := a[len(a)-1:][0]
 | |
| 
 | |
| 		for _, f := range files {
 | |
| 			a := strings.Split(path.Dir(f.Path), "/")
 | |
| 			fileDirName := a[len(a)-1:][0]
 | |
| 
 | |
| 			if dirName == fileDirName {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			// transform / to _ and some other chars...
 | |
| 			customName := "b0xfile_" + utils.FixName(f.Path) + ".go"
 | |
| 			finalList = append(finalList, customName)
 | |
| 
 | |
| 			exists := fileExists(cfg.Dest + customName)
 | |
| 			var mth string
 | |
| 			if exists {
 | |
| 				mth, err = getModification(cfg.Dest+customName, modTimeStart, modTimeEnd)
 | |
| 				if err != nil {
 | |
| 					panic(err)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			changed := mth != f.Modified
 | |
| 			if changed {
 | |
| 				changedList = append(changedList, f.OriginalPath)
 | |
| 			}
 | |
| 
 | |
| 			if !exists || changed {
 | |
| 				// creates file template and exec it
 | |
| 				t := new(template.Template)
 | |
| 				t.Set("file")
 | |
| 				t.Variables = struct {
 | |
| 					ConfigFile   string
 | |
| 					Now          string
 | |
| 					Pkg          string
 | |
| 					Path         string
 | |
| 					Name         string
 | |
| 					Dir          [][]string
 | |
| 					Tags         string
 | |
| 					Data         string
 | |
| 					Compression  *compression.Options
 | |
| 					Modified     string
 | |
| 					OriginalPath string
 | |
| 				}{
 | |
| 					ConfigFile:   filepath.Base(cfgPath),
 | |
| 					Now:          time.Now().String(),
 | |
| 					Pkg:          cfg.Pkg,
 | |
| 					Path:         f.Path,
 | |
| 					Name:         f.Name,
 | |
| 					Dir:          dirs.List,
 | |
| 					Tags:         f.Tags,
 | |
| 					Data:         f.Data,
 | |
| 					Compression:  cfg.Compression,
 | |
| 					Modified:     f.Modified,
 | |
| 					OriginalPath: f.OriginalPath,
 | |
| 				}
 | |
| 				tmpl, err := t.Exec()
 | |
| 				if err != nil {
 | |
| 					panic(err)
 | |
| 				}
 | |
| 
 | |
| 				// gofmt
 | |
| 				if cfg.Fmt {
 | |
| 					tmpl, err = format.Source(tmpl)
 | |
| 					if err != nil {
 | |
| 						panic(err)
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// write final execuTed template into the destination file
 | |
| 				if err := ioutil.WriteFile(cfg.Dest+customName, tmpl, 0640); err != nil {
 | |
| 					panic(err)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// remove b0xfiles when [clean] is true
 | |
| 	// it doesn't clean destination's folders
 | |
| 	if cfg.Clean {
 | |
| 		matches, err := filepath.Glob(cfg.Dest + "b0xfile_*.go")
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 
 | |
| 		// remove matched file if they aren't in the finalList
 | |
| 		// which contains the list of all files written by the
 | |
| 		// spread option
 | |
| 		for _, f := range matches {
 | |
| 			var found bool
 | |
| 			for _, name := range finalList {
 | |
| 				if strings.HasSuffix(f, name) {
 | |
| 					found = true
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if !found {
 | |
| 				err = os.Remove(f)
 | |
| 				if err != nil {
 | |
| 					panic(err)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// main b0x
 | |
| 	if lastHash != modHash {
 | |
| 		log.Printf("fileb0x: took [%dms] to write [%s] from config file [%s] at [%s]",
 | |
| 			time.Since(startTime).Nanoseconds()/1e6, cfg.Dest+cfg.Output,
 | |
| 			filepath.Base(cfgPath), time.Now().String())
 | |
| 	} else {
 | |
| 		log.Printf("fileb0x: no changes detected")
 | |
| 	}
 | |
| 
 | |
| 	// log changed files
 | |
| 	if cfg.Lcf && len(changedList) > 0 {
 | |
| 		log.Printf("fileb0x: list of changed files [%s]", strings.Join(changedList, " | "))
 | |
| 	}
 | |
| 
 | |
| 	if update {
 | |
| 		if !cfg.Updater.Enabled {
 | |
| 			panic("fileb0x: The updater is disabled, enable it in your config file!")
 | |
| 		}
 | |
| 
 | |
| 		// includes port when not present
 | |
| 		if !strings.HasSuffix(fUpdate, ":"+strconv.Itoa(cfg.Updater.Port)) {
 | |
| 			fUpdate += ":" + strconv.Itoa(cfg.Updater.Port)
 | |
| 		}
 | |
| 
 | |
| 		up = &updater.Updater{
 | |
| 			Server: fUpdate,
 | |
| 			Auth: updater.Auth{
 | |
| 				Username: cfg.Updater.Username,
 | |
| 				Password: cfg.Updater.Password,
 | |
| 			},
 | |
| 			Workers: cfg.Updater.Workers,
 | |
| 		}
 | |
| 
 | |
| 		// get file hashes from server
 | |
| 		if err := up.Init(); err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 
 | |
| 		// check if an update is available, then updates...
 | |
| 		if err := up.UpdateFiles(files); err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getModification(path string, start []byte, end []byte) (string, error) {
 | |
| 	file, err := os.Open(path)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	defer file.Close()
 | |
| 
 | |
| 	reader := bufio.NewReader(file)
 | |
| 	var data []byte
 | |
| 	for {
 | |
| 		line, _, err := reader.ReadLine()
 | |
| 		if err != nil {
 | |
| 			return "", err
 | |
| 		}
 | |
| 
 | |
| 		if !bytes.HasPrefix(line, start) || !bytes.HasSuffix(line, end) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		data = line
 | |
| 		break
 | |
| 	}
 | |
| 
 | |
| 	hash := bytes.TrimPrefix(data, start)
 | |
| 	hash = bytes.TrimSuffix(hash, end)
 | |
| 
 | |
| 	return string(hash), nil
 | |
| }
 | |
| 
 | |
| func fileExists(filename string) bool {
 | |
| 	_, err := os.Stat(filename)
 | |
| 	return err == nil
 | |
| }
 | |
| 
 | |
| func stringMD5Hex(data string) string {
 | |
| 	hash := md5.New()
 | |
| 	hash.Write([]byte(data))
 | |
| 	return fmt.Sprintf("%x", hash.Sum(nil))
 | |
| }
 |