// // rhimportd // // The Radio Helsinki Rivendell Import Daemon // // // Copyright (C) 2015-2016 Christian Pointner // // This file is part of rhimportd. // // rhimportd 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 // any later version. // // rhimportd 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 rhimportd. If not, see . // package main import ( "code.helsinki.at/rhrd-go/rddb" "code.helsinki.at/rhrd-go/rhimport" "encoding/json" "fmt" "net/http" "os" "path/filepath" "strings" "time" ) type watchDirRequestData struct { UserName string `json:"LOGIN_NAME"` ShowId uint `json:"SHOW_ID"` ClearShowCarts bool `json:"CLEAR_SHOW_CARTS"` MusicPoolGroup string `json:"MUSIC_POOL_GROUP"` Cart uint `json:"CART_NUMBER"` ClearCart bool `json:"CLEAR_CART"` Cut uint `json:"CUT_NUMBER"` Channels uint `json:"CHANNELS"` NormalizationLevel int `json:"NORMALIZATION_LEVEL"` AutotrimLevel int `json:"AUTOTRIM_LEVEL"` UseMetaData bool `json:"USE_METADATA"` SourceUri string `json:"SOURCE_URI"` SourceFilePolicy string `json:"SOURCE_FILE_POLICY"` } func newWatchDirRequestData(conf *rhimport.Config) *watchDirRequestData { rd := new(watchDirRequestData) rd.UserName = "" rd.ShowId = 0 rd.ClearShowCarts = false rd.MusicPoolGroup = "" rd.Cart = 0 rd.ClearCart = false rd.Cut = 0 rd.Channels = conf.ImportParamDefaults.Channels rd.NormalizationLevel = conf.ImportParamDefaults.NormalizationLevel rd.AutotrimLevel = conf.ImportParamDefaults.AutotrimLevel rd.UseMetaData = conf.ImportParamDefaults.UseMetaData rd.SourceUri = "" rd.SourceFilePolicy = "" return rd } type watchDirResponseData struct { ResponseCode int `json:"RESPONSE_CODE"` ErrorString string `json:"ERROR_STRING"` Cart uint `json:"CART_NUMBER"` Cut uint `json:"CUT_NUMBER"` SourceFile string `json:"SOURCE_FILE"` } func watchDirWriteResponse(filename string, resp *watchDirResponseData) { file, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, 0) if err != nil { rhl.Printf("watch-dir-ctrl: writing response failed: %s", err) return } encoder := json.NewEncoder(file) encoder.Encode(resp) file.Close() dstname := strings.TrimSuffix(filename, ".running") + ".done" os.Rename(filename, dstname) } func watchDirErrorResponse(filename string, code int, errStr, sourceFile string) { watchDirWriteResponse(filename, &watchDirResponseData{code, errStr, 0, 0, sourceFile}) } func watchDirResponse(filename string, result *rhimport.Result) { watchDirWriteResponse(filename, &watchDirResponseData{result.ResponseCode, result.ErrorString, result.Cart, result.Cut, result.SourceFile}) } func watchDirParseRequest(conf *rhimport.Config, db *rddb.DBChan, req *os.File) (ctx *rhimport.Context, err error) { decoder := json.NewDecoder(req) reqdata := newWatchDirRequestData(conf) if jsonerr := decoder.Decode(reqdata); jsonerr != nil { err = fmt.Errorf("Error parsing JSON response: %s", jsonerr) return } ctx = rhimport.NewContext(conf, db) ctx.UserName = reqdata.UserName ctx.Trusted = true ctx.ShowId = reqdata.ShowId ctx.ClearShowCarts = reqdata.ClearShowCarts ctx.GroupName = reqdata.MusicPoolGroup ctx.Cart = reqdata.Cart ctx.ClearCart = reqdata.ClearCart ctx.Cut = reqdata.Cut ctx.Channels = reqdata.Channels ctx.NormalizationLevel = reqdata.NormalizationLevel ctx.AutotrimLevel = reqdata.AutotrimLevel ctx.UseMetaData = reqdata.UseMetaData ctx.SourceUri = reqdata.SourceUri if reqdata.SourceFilePolicy != "" { err = ctx.SourceFilePolicy.FromString(reqdata.SourceFilePolicy) } return } func watchDirHandler(conf *rhimport.Config, db *rddb.DBChan, ctx *rhimport.Context, filename string) { rhdl.Printf("WatchDirHandler: request for '%s'", filename) var err error if err = ctx.SanityCheck(); err != nil { watchDirErrorResponse(filename, http.StatusBadRequest, err.Error(), "") return } var res *rhimport.Result if res, err = rhimport.FetchFile(ctx); err != nil { watchDirErrorResponse(filename, http.StatusInternalServerError, err.Error(), "") return } if res.ResponseCode != http.StatusOK { watchDirErrorResponse(filename, res.ResponseCode, res.ErrorString, "") return } if res, err = rhimport.NormalizeFile(ctx); err != nil { watchDirErrorResponse(filename, http.StatusInternalServerError, err.Error(), "") return } if res.ResponseCode != http.StatusOK { watchDirErrorResponse(filename, res.ResponseCode, res.ErrorString, "") return } if res, err = rhimport.ImportFile(ctx); err != nil { watchDirErrorResponse(filename, http.StatusInternalServerError, err.Error(), res.SourceFile) return } if res.ResponseCode == http.StatusOK { rhl.Println("ImportFile succesfully imported", ctx.SourceFile) } else { rhl.Println("ImportFile import of", ctx.SourceFile, "was unsuccesful") } watchDirResponse(filename, res) return } func watchDirRun(dirname string, conf *rhimport.Config, db *rddb.DBChan) bool { dir, err := os.Open(dirname) if err != nil { rhl.Printf("watch-dir-ctrl: %s", err) return false } defer dir.Close() var di os.FileInfo if di, err = dir.Stat(); err != nil { rhl.Printf("watch-dir-ctrl: %s", err) return false } if !di.IsDir() { rhl.Printf("watch-dir-ctrl: %s is not a directory", dirname) return false } var names []string if names, err = dir.Readdirnames(0); err != nil { rhl.Printf("watch-dir-ctrl: reading directory contents failed: %s", err) return false } for _, name := range names { if strings.HasSuffix(name, ".new") { srcname := filepath.Join(dir.Name(), name) rhdl.Printf("watch-dir-ctrl: found new file %s", srcname) var file *os.File if file, err = os.Open(srcname); err != nil { rhl.Printf("watch-dir-ctrl: error reading new file: %s", err) continue } if ctx, err := watchDirParseRequest(conf, db, file); err == nil { file.Close() dstname := strings.TrimSuffix(srcname, ".new") + ".running" os.Rename(srcname, dstname) go watchDirHandler(conf, db, ctx, dstname) } else { // ignoring files with json errors -> maybe the file has not been written completely file.Close() rhdl.Printf("watch-dir-ctrl: new file %s parser error: %s, ignoring for now", srcname, err) } } } return true } func StartWatchDir(dirname string, conf *rhimport.Config, db *rddb.DBChan) { for { rhl.Printf("watch-dir-ctrl: watching for files in %s", dirname) t := time.NewTicker(1 * time.Second) for { if !watchDirRun(dirname, conf, db) { break } <-t.C } t.Stop() rhdl.Printf("watch-dir-ctrl: restarting in 5 sec...") time.Sleep(5 * time.Second) } }