edsm-uploader

A platform independent uploader of Elite Dangerous journal files to EDSM
git clone git://archive.git.mtrnord.blog/MTRNord/edsm-uploader.git
Log | Files | Refs | LICENSE

journal.go (4060B)


      1 package journal
      2 
      3 import (
      4 	"bufio"
      5 	"bytes"
      6 	"encoding/json"
      7 	"log"
      8 	"os"
      9 	"strings"
     10 	"sync"
     11 	"time"
     12 
     13 	"github.com/MTRNord/edsm_uploader/datatypes"
     14 	"github.com/MTRNord/edsm_uploader/edsm"
     15 	"github.com/pkg/errors"
     16 
     17 	_ "github.com/mattn/go-sqlite3"
     18 )
     19 
     20 type FileHeader struct {
     21 	// Define the fields of the FileHeader struct here.
     22 }
     23 
     24 type Journal struct {
     25 	edsm       *edsm.EDSM
     26 	LastDate   *time.Time
     27 	fileHeader *datatypes.FileHeader
     28 	logger     *log.Logger
     29 }
     30 
     31 func NewJournal(edsm *edsm.EDSM, logger *log.Logger) *Journal {
     32 	// Read lastDate from latest.txt
     33 	file, err := os.OpenFile("latest.txt", os.O_RDWR|os.O_CREATE, 0755)
     34 	if err != nil {
     35 		logger.Printf("Error opening latest.txt: %s", err)
     36 	}
     37 	defer file.Close()
     38 	scanner := bufio.NewScanner(file)
     39 	var lastDate *time.Time
     40 	if scanner.Scan() {
     41 		text := scanner.Text()
     42 		// Check if the latest.txt is empty.
     43 		// If it is empty then we need to set the lastDate to nil.
     44 		if text == "" {
     45 			lastDate = nil
     46 		} else {
     47 			lastDateL, err := time.Parse(time.RFC3339, text)
     48 			if err != nil {
     49 				logger.Printf("Error parsing latest.txt: %s", err)
     50 			}
     51 			lastDate = &lastDateL
     52 		}
     53 	}
     54 
     55 	return &Journal{
     56 		// TODO: Parse the last date from the sqlite database.
     57 		// If there is no last date, then we need to start from the beginning and define it as nil.
     58 		edsm:       edsm,
     59 		LastDate:   lastDate,
     60 		fileHeader: nil,
     61 		logger:     logger,
     62 	}
     63 }
     64 
     65 func (j *Journal) ParseJournal(journalPath string) error {
     66 	splitPath := strings.Split(journalPath, "/")
     67 	j.logger.Printf("Parsing journal file: %s", splitPath[len(splitPath)-1])
     68 	file, err := os.Open(journalPath)
     69 	if err != nil {
     70 		return errors.WithStack(err)
     71 	}
     72 	defer file.Close()
     73 
     74 	scanner := bufio.NewScanner(file)
     75 	// Parse first line first
     76 	if scanner.Scan() {
     77 		err := j.parseLine(scanner.Text())
     78 		if err != nil {
     79 			return errors.WithStack(err)
     80 		}
     81 	}
     82 	var wg sync.WaitGroup
     83 	for scanner.Scan() {
     84 		wg.Add(1)
     85 		go func(line string) error {
     86 			defer wg.Done()
     87 			err := j.parseLine(line)
     88 			if err != nil {
     89 				return errors.WithStack(err)
     90 			}
     91 			return nil
     92 		}(scanner.Text())
     93 		time.Sleep(1 * time.Millisecond)
     94 	}
     95 	if err := scanner.Err(); err != nil {
     96 		return errors.WithStack(err)
     97 	}
     98 	wg.Wait()
     99 
    100 	return nil
    101 }
    102 
    103 func (j *Journal) storeLastDate(timestamp time.Time) {
    104 	if j.LastDate == nil || j.LastDate.Before(timestamp) {
    105 		j.LastDate = &timestamp
    106 		file, err := os.OpenFile("latest.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
    107 		if err != nil {
    108 			j.logger.Printf("Error opening latest.txt: %s", err)
    109 		}
    110 		defer file.Close()
    111 		_, err = file.WriteString(timestamp.Format(time.RFC3339))
    112 		if err != nil {
    113 			j.logger.Printf("Error writing to latest.txt: %s", err)
    114 		}
    115 	}
    116 }
    117 
    118 // This parses the journal line of the elite dangerous journal.
    119 // TODO: We need to store where we are in the journal so we can pick up where we left off.
    120 func (j *Journal) parseLine(line string) error {
    121 	// Parse json line as a JournalLine.
    122 	var journalLine datatypes.JournalLine
    123 	bytesString := []byte(line)
    124 	bytesString = bytes.Trim(bytesString, "\x00")
    125 	// Exit early if the line is empty
    126 	if len(bytesString) == 0 {
    127 		return nil
    128 	}
    129 	err := json.Unmarshal(bytesString, &journalLine)
    130 	if err != nil {
    131 		return errors.WithStack(err)
    132 	}
    133 
    134 	if journalLine.Event == "Fileheader" {
    135 		// Parse the json line as a FileHeader.
    136 		var fileHeader datatypes.FileHeader
    137 		json.Unmarshal([]byte(line), &fileHeader)
    138 
    139 		// Store the file header.
    140 		j.fileHeader = &fileHeader
    141 	}
    142 
    143 	// If we have a startdate make sure new lines are newer than the startdate.
    144 	parsedDate, err := time.Parse(time.RFC3339, journalLine.Timestamp)
    145 	if err != nil {
    146 		return errors.WithStack(err)
    147 	}
    148 	if j.LastDate != nil {
    149 		lastDate, err := time.Parse(time.RFC3339, j.LastDate.Format(time.RFC3339))
    150 		if err != nil {
    151 			return errors.WithStack(err)
    152 		}
    153 		if parsedDate.Before(lastDate) {
    154 			return nil
    155 		}
    156 	}
    157 
    158 	// Send the line to edsm.
    159 	err = j.edsm.SendJournalLine(j.fileHeader, line)
    160 	if err != nil {
    161 		return errors.WithStack(err)
    162 	}
    163 
    164 	// Store the last date.
    165 	j.storeLastDate(parsedDate)
    166 
    167 	return nil
    168 }