package logfile import ( "encoding/json" "fmt" "io" "os" "path/filepath" "sort" "time" "github.com/go-flucky/flucky/pkg/types" ) // 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 } // ReadTemperatures from a file and returns an array with temperatures func ReadTemperatures(temperatureLogfile string) ([]*types.Temperature, error) { if _, err := os.Stat(temperatureLogfile); os.IsNotExist(err) { return nil, fmt.Errorf("Can not find temperature logfile %v", temperatureLogfile) } temperatures := make([]*types.Temperature, 0) f, err := os.Open(temperatureLogfile) if err != nil { return nil, fmt.Errorf("Can not open temperature logfile %v", temperatureLogfile) } defer f.Close() temperatures, err = ReadTemperaturesCustom(f) if err != nil { return nil, fmt.Errorf("Can not read temperatures from logfile %v", temperatureLogfile) } return temperatures, nil } // ReadTemperaturesChannel reads temperatures from a channel until it is closed // and returns the temperature. func ReadTemperaturesChannel(temperatureChannel <-chan *types.Temperature) []*types.Temperature { temperatures := make([]*types.Temperature, 0) for { select { case temperature, more := <-temperatureChannel: if more { temperatures = append(temperatures, temperature) } default: return temperatures } } } // ReadTemperaturesCustom from a custom reader and returns an array with // temperatures func ReadTemperaturesCustom(r io.Reader) ([]*types.Temperature, error) { temperatures := make([]*types.Temperature, 0) decoder := json.NewDecoder(r) err := decoder.Decode(&temperatures) if err != nil { return nil, fmt.Errorf("Can not decode temperatures from reader: %v", err) } return temperatures, nil } // 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) }) } // WriteTemperatures encode temperatures into json and write it into a file. // Compression can be enabled over a bolean parameter func WriteTemperatures(temperatures []*types.Temperature, temperatureLogfile string, compression bool) error { allTemperatures := make([]*types.Temperature, 0) if _, err := os.Stat(temperatureLogfile); os.IsNotExist(err) { err := os.MkdirAll(filepath.Dir(temperatureLogfile), 0755) if err != nil { return fmt.Errorf("Can not create directory %v to write temperatures into the logfile", filepath.Dir(temperatureLogfile)) } f, err := os.Create(temperatureLogfile) if err != nil { return fmt.Errorf("Can not create file %v: %v", temperatureLogfile, err) } defer f.Close() } else { f, err := os.Open(temperatureLogfile) if err != nil { return fmt.Errorf("Can not open file %v: %v", temperatureLogfile, err) } defer f.Close() savedTemperatures, err := ReadTemperaturesCustom(f) if err != nil { return fmt.Errorf("Can not read temperatures from logfile %v: %v", temperatureLogfile, err) } allTemperatures = append(allTemperatures, savedTemperatures...) } f, err := os.Create(temperatureLogfile) if err != nil { return fmt.Errorf("Can not create file %v: %v", temperatureLogfile, err) } defer f.Close() allTemperatures = append(allTemperatures, temperatures...) err = WriteTemperaturesCustom(allTemperatures, f, compression) if err != nil { return fmt.Errorf("Can not write temperatures to logfile %v: %v", temperatureLogfile, err) } return nil } // WriteTemperaturesCustom encode temperatures into json and write it into // custom writer. Compression can be enabled over a bolean parameter func WriteTemperaturesCustom(temperatures []*types.Temperature, w io.Writer, compression bool) error { if compression { temperatures = CompressTemperature(temperatures) } writeCreationDate(temperatures) jsonEncoder := json.NewEncoder(w) jsonEncoder.SetIndent("", " ") err := jsonEncoder.Encode(temperatures) if err != nil { return fmt.Errorf("Can not encode temperatures: %v", err) } return nil } func writeCreationDate(temperatures []*types.Temperature) { now := time.Now() for _, temperature := range temperatures { if temperature.CreationDate == nil { temperature.CreationDate = &now } } }