1// Copyright (C) 2015 The Syncthing Authors.
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this file,
5// You can obtain one at https://mozilla.org/MPL/2.0/.
6
7package model
8
9import (
10	"time"
11
12	"github.com/syncthing/syncthing/lib/events"
13	"github.com/syncthing/syncthing/lib/sync"
14)
15
16type folderState int
17
18const (
19	FolderIdle folderState = iota
20	FolderScanning
21	FolderScanWaiting
22	FolderSyncWaiting
23	FolderSyncPreparing
24	FolderSyncing
25	FolderCleaning
26	FolderCleanWaiting
27	FolderError
28)
29
30func (s folderState) String() string {
31	switch s {
32	case FolderIdle:
33		return "idle"
34	case FolderScanning:
35		return "scanning"
36	case FolderScanWaiting:
37		return "scan-waiting"
38	case FolderSyncWaiting:
39		return "sync-waiting"
40	case FolderSyncPreparing:
41		return "sync-preparing"
42	case FolderSyncing:
43		return "syncing"
44	case FolderCleaning:
45		return "cleaning"
46	case FolderCleanWaiting:
47		return "clean-waiting"
48	case FolderError:
49		return "error"
50	default:
51		return "unknown"
52	}
53}
54
55type stateTracker struct {
56	folderID string
57	evLogger events.Logger
58
59	mut     sync.Mutex
60	current folderState
61	err     error
62	changed time.Time
63}
64
65func newStateTracker(id string, evLogger events.Logger) stateTracker {
66	return stateTracker{
67		folderID: id,
68		evLogger: evLogger,
69		mut:      sync.NewMutex(),
70	}
71}
72
73// setState sets the new folder state, for states other than FolderError.
74func (s *stateTracker) setState(newState folderState) {
75	if newState == FolderError {
76		panic("must use setError")
77	}
78
79	s.mut.Lock()
80	defer s.mut.Unlock()
81
82	if newState == s.current {
83		return
84	}
85
86	/* This should hold later...
87	if s.current != FolderIdle && (newState == FolderScanning || newState == FolderSyncing) {
88		panic("illegal state transition " + s.current.String() + " -> " + newState.String())
89	}
90	*/
91
92	eventData := map[string]interface{}{
93		"folder": s.folderID,
94		"to":     newState.String(),
95		"from":   s.current.String(),
96	}
97
98	if !s.changed.IsZero() {
99		eventData["duration"] = time.Since(s.changed).Seconds()
100	}
101
102	s.current = newState
103	s.changed = time.Now().Truncate(time.Second)
104
105	s.evLogger.Log(events.StateChanged, eventData)
106}
107
108// getState returns the current state, the time when it last changed, and the
109// current error or nil.
110func (s *stateTracker) getState() (current folderState, changed time.Time, err error) {
111	s.mut.Lock()
112	current, changed, err = s.current, s.changed, s.err
113	s.mut.Unlock()
114	return
115}
116
117// setError sets the folder state to FolderError with the specified error or
118// to FolderIdle if the error is nil
119func (s *stateTracker) setError(err error) {
120	s.mut.Lock()
121	defer s.mut.Unlock()
122
123	eventData := map[string]interface{}{
124		"folder": s.folderID,
125		"from":   s.current.String(),
126	}
127
128	if err != nil {
129		eventData["error"] = err.Error()
130		s.current = FolderError
131	} else {
132		s.current = FolderIdle
133	}
134
135	eventData["to"] = s.current.String()
136
137	if !s.changed.IsZero() {
138		eventData["duration"] = time.Since(s.changed).Seconds()
139	}
140
141	s.err = err
142	s.changed = time.Now().Truncate(time.Second)
143
144	s.evLogger.Log(events.StateChanged, eventData)
145}
146