// checkShortIDs verifies that the configuration won't result in duplicate // short ID:s; that is, that the devices in the cluster all have unique // initial 64 bits. func checkShortIDs(cfg *config.Wrapper) error { exists := make(map[uint64]protocol.DeviceID) for deviceID := range cfg.Devices() { shortID := deviceID.Short() if otherID, ok := exists[shortID]; ok { return fmt.Errorf("%v in conflict with %v", deviceID, otherID) } exists[shortID] = deviceID } return nil }
// Creates a new progress emitter which emits DownloadProgress events every // interval. func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter { t := &ProgressEmitter{ stop: make(chan struct{}), registry: make(map[string]*sharedPullerState), last: make(map[string]map[string]*pullerProgress), timer: time.NewTimer(time.Millisecond), } t.Changed(cfg.Raw()) cfg.Subscribe(t) return t }
func NewBlockFinder(db *leveldb.DB, cfg *config.Wrapper) *BlockFinder { if blockFinder != nil { return blockFinder } f := &BlockFinder{ db: db, } f.Changed(cfg.Raw()) cfg.Subscribe(f) return f }
// NewProgressEmitter creates a new progress emitter which emits // DownloadProgress events every interval. func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter { t := &ProgressEmitter{ stop: make(chan struct{}), registry: make(map[string]*sharedPullerState), last: make(map[string]map[string]*pullerProgress), timer: time.NewTimer(time.Millisecond), mut: sync.NewMutex(), } t.CommitConfiguration(config.Configuration{}, cfg.Raw()) cfg.Subscribe(t) return t }
func newUsageReportingManager(m *model.Model, cfg *config.Wrapper) *usageReportingManager { mgr := &usageReportingManager{ model: m, } // Start UR if it's enabled. mgr.CommitConfiguration(config.Configuration{}, cfg.Raw()) // Listen to future config changes so that we can start and stop as // appropriate. cfg.Subscribe(mgr) return mgr }
func NewBlockFinder(db *leveldb.DB, cfg *config.Wrapper) *BlockFinder { if blockFinder != nil { return blockFinder } f := &BlockFinder{ db: db, mut: sync.NewRWMutex(), } f.CommitConfiguration(config.Configuration{}, cfg.Raw()) cfg.Subscribe(f) return f }
func sanityCheckFolders(cfg *config.Wrapper, m *model.Model) { nextFolder: for id, folder := range cfg.Folders() { if folder.Invalid != "" { continue } m.AddFolder(folder) fi, err := os.Stat(folder.Path) if m.CurrentLocalVersion(id) > 0 { // Safety check. If the cached index contains files but the // folder doesn't exist, we have a problem. We would assume // that all files have been deleted which might not be the case, // so mark it as invalid instead. if err != nil || !fi.IsDir() { l.Warnf("Stopping folder %q - path does not exist, but has files in index", folder.ID) cfg.InvalidateFolder(id, "folder path missing") continue nextFolder } else if !folder.HasMarker() { l.Warnf("Stopping folder %q - path exists, but folder marker missing, check for mount issues", folder.ID) cfg.InvalidateFolder(id, "folder marker missing") continue nextFolder } } else if os.IsNotExist(err) { // If we don't have any files in the index, and the directory // doesn't exist, try creating it. err = os.MkdirAll(folder.Path, 0700) if err != nil { l.Warnf("Stopping folder %q - %v", folder.ID, err) cfg.InvalidateFolder(id, err.Error()) continue nextFolder } err = folder.CreateMarker() } else if !folder.HasMarker() { // If we don't have any files in the index, and the path does exist // but the marker is not there, create it. err = folder.CreateMarker() } if err != nil { // If there was another error or we could not create the // path, the folder is invalid. l.Warnf("Stopping folder %q - %v", folder.ID, err) cfg.InvalidateFolder(id, err.Error()) continue nextFolder } } }
// NewModel creates and starts a new model. The model starts in read-only mode, // where it sends index information to connected peers and responds to requests // for file data without altering the local folder in any way. func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, clientVersion string, ldb *leveldb.DB) *Model { m := &Model{ Supervisor: suture.New("model", suture.Spec{ Log: func(line string) { if debug { l.Debugln(line) } }, }), cfg: cfg, db: ldb, finder: db.NewBlockFinder(ldb, cfg), progressEmitter: NewProgressEmitter(cfg), id: id, shortID: id.Short(), deviceName: deviceName, clientName: clientName, clientVersion: clientVersion, folderCfgs: make(map[string]config.FolderConfiguration), folderFiles: make(map[string]*db.FileSet), folderDevices: make(map[string][]protocol.DeviceID), deviceFolders: make(map[protocol.DeviceID][]string), deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference), folderIgnores: make(map[string]*ignore.Matcher), folderRunners: make(map[string]service), folderStatRefs: make(map[string]*stats.FolderStatisticsReference), protoConn: make(map[protocol.DeviceID]protocol.Connection), rawConn: make(map[protocol.DeviceID]io.Closer), deviceVer: make(map[protocol.DeviceID]string), reqValidationCache: make(map[string]time.Time), fmut: sync.NewRWMutex(), pmut: sync.NewRWMutex(), rvmut: sync.NewRWMutex(), } if cfg.Options().ProgressUpdateIntervalS > -1 { go m.progressEmitter.Serve() } return m }
func setupGUI(cfg *config.Wrapper, m *model.Model) { opts := cfg.Options() guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey) if guiCfg.Enabled && guiCfg.Address != "" { addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address) if err != nil { l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err) } else { var hostOpen, hostShow string switch { case addr.IP == nil: hostOpen = "localhost" hostShow = "0.0.0.0" case addr.IP.IsUnspecified(): hostOpen = "localhost" hostShow = addr.IP.String() default: hostOpen = addr.IP.String() hostShow = hostOpen } var proto = "http" if guiCfg.UseTLS { proto = "https" } urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port))) l.Infoln("Starting web GUI on", urlShow) err := startGUI(guiCfg, guiAssets, m) if err != nil { l.Fatalln("Cannot start GUI:", err) } if opts.StartBrowser && !noBrowser && !stRestarting { urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port))) openURL(urlOpen) } } } }
// NewModel creates and starts a new model. The model starts in read-only mode, // where it sends index information to connected peers and responds to requests // for file data without altering the local folder in any way. func NewModel(cfg *config.Wrapper, deviceName, clientName, clientVersion string, ldb *leveldb.DB) *Model { m := &Model{ cfg: cfg, db: ldb, deviceName: deviceName, clientName: clientName, clientVersion: clientVersion, folderCfgs: make(map[string]config.FolderConfiguration), folderFiles: make(map[string]*db.FileSet), folderDevices: make(map[string][]protocol.DeviceID), deviceFolders: make(map[protocol.DeviceID][]string), deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference), folderIgnores: make(map[string]*ignore.Matcher), folderRunners: make(map[string]service), folderStatRefs: make(map[string]*stats.FolderStatisticsReference), folderState: make(map[string]folderState), folderStateChanged: make(map[string]time.Time), protoConn: make(map[protocol.DeviceID]protocol.Connection), rawConn: make(map[protocol.DeviceID]io.Closer), deviceVer: make(map[protocol.DeviceID]string), finder: db.NewBlockFinder(ldb, cfg), progressEmitter: NewProgressEmitter(cfg), } if cfg.Options().ProgressUpdateIntervalS > -1 { go m.progressEmitter.Serve() } var timeout = 20 * 60 // seconds if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 { it, err := strconv.Atoi(t) if err == nil { timeout = it } } deadlockDetect(&m.fmut, time.Duration(timeout)*time.Second) deadlockDetect(&m.smut, time.Duration(timeout)*time.Second) deadlockDetect(&m.pmut, time.Duration(timeout)*time.Second) return m }
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription) { opts := cfg.Options() guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey) if guiCfg.Enabled && guiCfg.Address != "" { addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address) if err != nil { l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err) } else { var hostOpen, hostShow string switch { case addr.IP == nil: hostOpen = "localhost" hostShow = "0.0.0.0" case addr.IP.IsUnspecified(): hostOpen = "localhost" hostShow = addr.IP.String() default: hostOpen = addr.IP.String() hostShow = hostOpen } var proto = "http" if guiCfg.UseTLS { proto = "https" } urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port))) l.Infoln("Starting web GUI on", urlShow) api, err := newAPISvc(myID, guiCfg, guiAssets, m, apiSub) if err != nil { l.Fatalln("Cannot start GUI:", err) } cfg.Subscribe(api) mainSvc.Add(api) if opts.StartBrowser && !noBrowser && !stRestarting { urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port))) // Can potentially block if the utility we are invoking doesn't // fork, and just execs, hence keep it in it's own routine. go openURL(urlOpen) } } } }