package logfile import ( "os" "path/filepath" "regexp" "sort" "time" "github.com/go-flucky/flucky/pkg/types" ) var validUUID = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") var timeFormat = "2006-01-02 15:04:05.999999999 -0700" // AppendTemperatures with temperature values from a logfile. As additional // option it's possible to compress the temperature data. func AppendTemperatures(logfile Logfile, compression bool, temperatures []*types.Temperature) error { allTemperatures := make([]*types.Temperature, 0) if _, err := os.Stat(logfile.GetLogfile()); err == nil { temperaturesFromLogfile, err := logfile.ReadTemperatures() if err != nil { return err } allTemperatures = append(allTemperatures, temperaturesFromLogfile...) } allTemperatures = append(allTemperatures, temperatures...) if compression { allTemperatures = CompressTemperature(allTemperatures) } err := logfile.WriteTemperatures(allTemperatures) if err != nil { return err } return nil } // CompressTemperature compresses the temperatures from an array. It is checked // whether the measured temperature of a value corresponds to that of the // predecessor. If this is the case, the current value is discarded and the // validity date of the predecessor value is set to that of the current value. // No information is lost as a result. The validity period of the measured value // is thereby exclusively increased. func CompressTemperature(temperatures []*types.Temperature) []*types.Temperature { compressedTemperatures := make([]*types.Temperature, 0) lastTemperatureBySensors := make(map[string]*types.Temperature, 0) // Sort all measured temperatures beforehand by the starting validity date to // avoid errors when compressing the temperatures. SortTemperatures(temperatures) for _, temperature := range temperatures { if lastTemperatureBySensor, ok := lastTemperatureBySensors[temperature.SensorID]; ok { if lastTemperatureBySensor.TemperatureValue == temperature.TemperatureValue { lastTemperatureBySensors[temperature.SensorID].TemperatureTillDate = temperature.TemperatureTillDate now := time.Now() lastTemperatureBySensors[temperature.SensorID].UpdateDate = &now } else { compressedTemperatures = append(compressedTemperatures, lastTemperatureBySensors[temperature.SensorID]) lastTemperatureBySensors[temperature.SensorID] = temperature } } else { lastTemperatureBySensors[temperature.SensorID] = temperature } } // Copy all remaining entries from the map into the array for _, lastTemperatureBySensor := range lastTemperatureBySensors { compressedTemperatures = append(compressedTemperatures, lastTemperatureBySensor) } return compressedTemperatures } // New returns a log file with basic functions for reading and writing data. The // file extension of the logfile is taken into account to format the logfile // into the correct format. func New(logfile string) Logfile { ext := filepath.Ext(logfile) switch ext { case ".csv": return &csvLogfile{ logfile: logfile, } case ".json": return &jsonLogfile{ logfile: logfile, } case ".xml": return &xmlLogfile{ logfile: logfile, } default: return &jsonLogfile{ logfile: logfile, } } } // SplittTemperatures into multiple arrays. The Size can be defined by // temperatureSplitBy parameter. func SplittTemperatures(temperatures []*types.Temperature, templeratureSplitBy int) [][]*types.Temperature { splittedTemperatures := make([][]*types.Temperature, 0) newTemperatures := make([]*types.Temperature, 0) for _, temperature := range temperatures { if len(newTemperatures) == templeratureSplitBy { splittedTemperatures = append(splittedTemperatures, newTemperatures) newTemperatures = make([]*types.Temperature, 0) } newTemperatures = append(newTemperatures, temperature) } splittedTemperatures = append(splittedTemperatures, newTemperatures) return splittedTemperatures } // SortTemperatures by TemperatureFromDate func SortTemperatures(temperatures []*types.Temperature) { sort.SliceStable(temperatures, func(i int, j int) bool { return temperatures[i].TemperatureFromDate.Before(temperatures[j].TemperatureFromDate) }) } // ValidateTemperatures Checks if the temperature data is valid. // - Check the temperature id (uuid) // - Checks whether the time specifications are historically in a sequence. // - Check the sensor id (uuid) func ValidateTemperatures(temperatures []*types.Temperature) error { for _, temperature := range temperatures { if !validUUID.MatchString(temperature.TemperatureID) { return errorNoValidTemperatureID } else if temperature.TemperatureValue == 0 { return errorNoValidMesuredValue } else if temperature.TemperatureFromDate.After(temperature.TemperatureTillDate) { return errorNoValidTimePeriods } else if !validUUID.MatchString(temperature.SensorID) { return errorNoValidSensorID } else if temperature.CreationDate.After(*temperature.UpdateDate) && temperature.UpdateDate != nil { return errorNoValidTimePeriods } } return nil } func writeCreationDate(temperatures []*types.Temperature) { now := time.Now() for _, temperature := range temperatures { if temperature.CreationDate == nil { temperature.CreationDate = &now } } }