File: trace.go

package info (click to toggle)
mirrorbits 0.6.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 984 kB
  • sloc: sh: 675; makefile: 93
file content (141 lines) | stat: -rw-r--r-- 3,283 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright (c) 2014-2019 Ludovic Fauvet
// Licensed under the MIT license

package scan

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strconv"
	"time"

	. "github.com/etix/mirrorbits/config"
	"github.com/etix/mirrorbits/core"
	"github.com/etix/mirrorbits/database"
	"github.com/etix/mirrorbits/mirrors"
	"github.com/etix/mirrorbits/utils"
)

var (
	userAgent      = "Mirrorbits/" + core.VERSION + " TRACE"
	clientTimeout  = time.Duration(20 * time.Second)
	clientDeadline = time.Duration(40 * time.Second)

	// ErrNoTrace is returned when no trace file is found
	ErrNoTrace = errors.New("No trace file")
)

// Trace is the internal trace handler
type Trace struct {
	redis      *database.Redis
	transport  http.Transport
	httpClient http.Client
	stop       <-chan struct{}
}

// NewTraceHandler returns a new instance of the trace file handler.
// Trace files are used to compute the time offset between a mirror
// and the local repository.
func NewTraceHandler(redis *database.Redis, stop <-chan struct{}) *Trace {
	t := &Trace{
		redis: redis,
		stop:  stop,
	}

	t.transport = http.Transport{
		DisableKeepAlives:   true,
		MaxIdleConnsPerHost: 0,
		Dial: func(network, addr string) (net.Conn, error) {
			deadline := time.Now().Add(clientDeadline)
			c, err := net.DialTimeout(network, addr, clientTimeout)
			if err != nil {
				return nil, err
			}
			c.SetDeadline(deadline)
			return c, nil
		},
	}

	t.httpClient = http.Client{
		Transport: &t.transport,
	}

	return t
}

// GetLastUpdate connects in HTTP to the mirror to get the latest
// trace file and computes the offset of the mirror.
func (t *Trace) GetLastUpdate(mirror mirrors.Mirror) error {
	traceFile := GetConfig().TraceFileLocation

	if len(traceFile) == 0 {
		return ErrNoTrace
	}

	log.Debugf("Getting latest trace file for %s...", mirror.Name)

	// Prepare the mirror URL
	var mirrorURL string
	if utils.HasAnyPrefix(mirror.HttpURL, "http://", "https://") {
		mirrorURL = mirror.HttpURL
	} else if mirror.HttpsUp == true {
		mirrorURL = "https://" + mirror.HttpURL
	} else {
		mirrorURL = "http://" + mirror.HttpURL
	}

	// Prepare the HTTP request
	req, err := http.NewRequest("GET", utils.ConcatURL(mirrorURL, traceFile), nil)
	req.Header.Set("User-Agent", userAgent)
	req.Close = true

	// Prepare contexts
	ctx, cancel := context.WithTimeout(req.Context(), clientDeadline)
	ctx = context.WithValue(ctx, core.ContextMirrorID, mirror.ID)
	req = req.WithContext(ctx)
	defer cancel()

	go func() {
		select {
		case <-t.stop:
			cancel()
		case <-ctx.Done():
		}
	}()

	resp, err := t.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	scanner := bufio.NewScanner(bufio.NewReader(resp.Body))
	scanner.Split(bufio.ScanWords)
	scanner.Scan()
	if err := scanner.Err(); err != nil {
		return err
	}

	timestamp, err := strconv.ParseInt(scanner.Text(), 10, 64)
	if err != nil {
		return err
	}

	conn := t.redis.Get()
	defer conn.Close()

	_, err = conn.Do("HSET", fmt.Sprintf("MIRROR_%d", mirror.ID), "lastModTime", timestamp)
	if err != nil {
		return err
	}

	// Publish an update on redis
	database.Publish(conn, database.MIRROR_UPDATE, strconv.Itoa(mirror.ID))

	log.Debugf("[%s] trace last sync: %s", mirror.Name, time.Unix(timestamp, 0))
	return nil
}