2024-05-02 15:35:00 +01:00
|
|
|
package pis
|
|
|
|
|
2024-10-21 21:04:37 +01:00
|
|
|
import (
|
|
|
|
"archive/tar"
|
|
|
|
"compress/gzip"
|
2024-11-12 12:05:30 +00:00
|
|
|
"encoding/json"
|
2024-10-21 21:04:37 +01:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2024-10-24 20:39:24 +01:00
|
|
|
|
2024-11-13 00:28:11 +00:00
|
|
|
"git.fjla.uk/owlboard/timetable-mgr/dbAccess"
|
2024-10-24 20:39:24 +01:00
|
|
|
"git.fjla.uk/owlboard/timetable-mgr/log"
|
|
|
|
"go.uber.org/zap"
|
2024-10-21 21:04:37 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
destPath = "/tmp/pis.tar.gz"
|
|
|
|
extractPath = "/tmp/extract"
|
|
|
|
)
|
|
|
|
|
2024-05-02 15:35:00 +01:00
|
|
|
// Downloads the release tarball, extracts then applies to database
|
|
|
|
func runUpdate(tarballUrl string) error {
|
2024-11-11 11:03:25 +00:00
|
|
|
log.Info("PIS Update Process started")
|
2024-10-21 21:04:37 +01:00
|
|
|
err := downloadFile(tarballUrl, destPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract to disk
|
|
|
|
file, err := os.Open(destPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-11-11 11:03:25 +00:00
|
|
|
log.Info("Saved PIS Release to disk")
|
2024-10-21 21:04:37 +01:00
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
if err := extractFiles(file, extractPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-11-11 11:03:25 +00:00
|
|
|
log.Info("Extracted PIS Release")
|
2024-10-21 21:04:37 +01:00
|
|
|
|
|
|
|
// Load YAML to string
|
|
|
|
pisData, err := extractYamlData(extractPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-10-22 20:50:08 +01:00
|
|
|
pisSlice, err := processYaml(pisData)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-11-11 11:03:25 +00:00
|
|
|
log.Info("Loaded PIS Files to Slice")
|
2024-10-22 20:50:08 +01:00
|
|
|
|
2024-11-13 00:28:11 +00:00
|
|
|
// Temporarily use "pis_testing" collection to ensure all works as expected
|
|
|
|
err = dbAccess.DropCollection("pis_testing")
|
2024-11-12 12:11:46 +00:00
|
|
|
if err != nil {
|
2024-11-13 00:28:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
count, err := dbAccess.PutPisData(pisSlice)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2024-11-12 12:11:46 +00:00
|
|
|
}
|
2024-11-13 00:28:11 +00:00
|
|
|
|
|
|
|
log.Info("Insterted new PIS Data", zap.Int64("PIS Codes", count))
|
|
|
|
|
|
|
|
err = dbAccess.CreatePisIndeces()
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Failed to create PIS Indeces, poor performance expected", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
2024-10-21 21:04:37 +01:00
|
|
|
// Cleanup files
|
|
|
|
cleanupFiles(destPath, extractPath)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Download the tarball to disk
|
|
|
|
func downloadFile(url, filepath string) error {
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-11-11 11:03:25 +00:00
|
|
|
log.Info("PIS Release downloaded")
|
2024-10-21 21:04:37 +01:00
|
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
out, err := os.Create(filepath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer out.Close()
|
|
|
|
|
|
|
|
_, err = io.Copy(out, resp.Body)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract tarball to disk
|
|
|
|
func extractFiles(gzipStream io.Reader, dest string) error {
|
|
|
|
uncompressedStream, err := gzip.NewReader(gzipStream)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer uncompressedStream.Close()
|
2024-11-11 11:03:25 +00:00
|
|
|
log.Info("Extracting PIS File")
|
2024-10-21 21:04:37 +01:00
|
|
|
|
|
|
|
tarReader := tar.NewReader(uncompressedStream)
|
|
|
|
|
|
|
|
for {
|
|
|
|
header, err := tarReader.Next()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-10-24 20:49:12 +01:00
|
|
|
// Handle pax_global_header or other unsupported types
|
|
|
|
if header.Typeflag == tar.TypeXGlobalHeader || header.Name == "pax_global_header" {
|
|
|
|
// Skip this special header file
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-10-21 21:04:37 +01:00
|
|
|
filePath := filepath.Join(dest, header.Name)
|
|
|
|
|
|
|
|
switch header.Typeflag {
|
|
|
|
case tar.TypeDir:
|
|
|
|
if err := os.MkdirAll(filepath.Join(dest, header.Name), 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case tar.TypeReg:
|
|
|
|
outFile, err := os.Create(filePath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := io.Copy(outFile, tarReader); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
outFile.Close()
|
|
|
|
default:
|
2024-10-24 20:39:24 +01:00
|
|
|
log.Warn("Unable to handle filetype", zap.String("Typeflag", string(header.Typeflag)), zap.String("Filename", header.Name))
|
2024-10-21 21:04:37 +01:00
|
|
|
}
|
|
|
|
}
|
2024-05-02 15:35:00 +01:00
|
|
|
return nil
|
|
|
|
}
|
2024-10-21 21:04:37 +01:00
|
|
|
|
|
|
|
// Return YAML PIS files as a string
|
|
|
|
func extractYamlData(dir string) (string, error) {
|
|
|
|
var allContent strings.Builder // Using a string builder to accumulate content
|
|
|
|
|
|
|
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err // Only returning error since Walk callback doesn't accept string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the path contains 'pis' and has a .yaml or .yml extension
|
|
|
|
if strings.Contains(path, "/pis/") && !info.IsDir() &&
|
|
|
|
(strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
|
2024-10-24 20:39:24 +01:00
|
|
|
log.Debug("Processing YAML", zap.String("directory", path))
|
2024-10-21 21:04:37 +01:00
|
|
|
|
|
|
|
file, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to open YAML file %s: %v", path, err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
content, err := io.ReadAll(file)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read YAML file %s: %v", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Accumulate content from each YAML file in 'pis' directory
|
|
|
|
allContent.Write(content)
|
|
|
|
allContent.WriteString("\n") // Add a newline between file contents
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the accumulated content as a single string
|
|
|
|
return allContent.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleans up downloaded and extracted files
|
|
|
|
func cleanupFiles(paths ...string) {
|
|
|
|
for _, path := range paths {
|
|
|
|
err := os.RemoveAll(path)
|
|
|
|
if err != nil {
|
2024-10-24 20:39:24 +01:00
|
|
|
log.Warn("Error removing file", zap.String("path", path), zap.Error(err))
|
2024-10-21 21:04:37 +01:00
|
|
|
}
|
|
|
|
}
|
2024-11-11 11:03:25 +00:00
|
|
|
log.Info("Removed PIS Release files")
|
2024-10-21 21:04:37 +01:00
|
|
|
}
|
2024-11-12 12:05:30 +00:00
|
|
|
|
|
|
|
// Saves pisSlice to file
|
|
|
|
func DEBUG_saveSliceToFile(pisSlice interface{}, filename string) error {
|
|
|
|
data, err := json.MarshalIndent(pisSlice, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error marshalling slice to JSON: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
file, err := os.Create(filename)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating file: %v", err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
_, err = file.Write(data)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error writing JSON to file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|