func NewDatabaseContext(dbName string, bucket base.Bucket) (*DatabaseContext, error) { context := &DatabaseContext{ Name: dbName, Bucket: bucket, tapNotifier: sync.NewCond(&sync.Mutex{}), } var err error context.sequences, err = newSequenceAllocator(bucket) if err != nil { return nil, err } tapFeed, err := bucket.StartTapFeed(walrus.TapArguments{Backfill: walrus.TapNoBackfill}) if err != nil { return nil, err } // Start a goroutine to broadcast to the tapNotifier whenever a document changes: go func() { for event := range tapFeed.Events() { if event.Opcode == walrus.TapMutation || event.Opcode == walrus.TapDeletion { key := string(event.Key) if strings.HasPrefix(key, "_sync:") && !strings.HasPrefix(key, "_sync:user") && !strings.HasPrefix(key, "_sync:role") { continue // ignore metadata docs (sequence counter, attachments, local docs...) } base.LogTo("Changes", "Notifying that %q changed (key=%q)", dbName, event.Key) context.tapNotifier.Broadcast() } } }() return context, nil }
// Starts a changeListener on a given Bucket. func (listener *changeListener) Start(bucket base.Bucket) error { listener.bucket = bucket tapFeed, err := bucket.StartTapFeed(walrus.TapArguments{Backfill: walrus.TapNoBackfill}) if err != nil { return err } listener.tapFeed = tapFeed listener.counter = 1 listener.tapNotifier = sync.NewCond(&sync.Mutex{}) // Start a goroutine to broadcast to the tapNotifier whenever a channel or user/role changes: go func() { defer listener.notify("") for event := range tapFeed.Events() { if event.Opcode == walrus.TapMutation || event.Opcode == walrus.TapDeletion { key := string(event.Key) if strings.HasPrefix(key, kChannelLogKeyPrefix) || strings.HasPrefix(key, auth.UserKeyPrefix) || strings.HasPrefix(key, auth.RoleKeyPrefix) { listener.notify(key) } } } }() return nil }
// Creates a new Shadower. func NewShadower(context *DatabaseContext, bucket base.Bucket, docIDPattern *regexp.Regexp) (*Shadower, error) { tapFeed, err := bucket.StartTapFeed(walrus.TapArguments{Backfill: 0}) if err != nil { return nil, err } s := &Shadower{context: context, bucket: bucket, tapFeed: tapFeed, docIDPattern: docIDPattern} go s.readTapFeed() return s, nil }
// Starts a changeListener on a given Bucket. func (listener *changeListener) Start(bucket base.Bucket, trackDocs bool) error { listener.bucket = bucket tapFeed, err := bucket.StartTapFeed(walrus.TapArguments{Backfill: walrus.TapNoBackfill}) if err != nil { return err } listener.tapFeed = tapFeed listener.counter = 1 listener.keyCounts = map[string]uint64{} listener.tapNotifier = sync.NewCond(&sync.Mutex{}) if trackDocs { listener.DocChannel = make(chan walrus.TapEvent, 100) } // Start a goroutine to broadcast to the tapNotifier whenever a channel or user/role changes: go func() { defer func() { listener.notify("") if listener.DocChannel != nil { close(listener.DocChannel) } }() for event := range tapFeed.Events() { if event.Opcode == walrus.TapMutation || event.Opcode == walrus.TapDeletion { key := string(event.Key) if strings.HasPrefix(key, kChannelLogKeyPrefix) { if listener.OnChannelChanged != nil { channelName := string(event.Key)[len(kChannelLogKeyPrefix):] // Notify the client synchronously via a fn call, instead of by writing // to a channel, to ensure that the client can cache the updated channel // log before any subsequent document change is processed. listener.OnChannelChanged(channelName, event.Value) } listener.notify(key) } else if strings.HasPrefix(key, auth.UserKeyPrefix) || strings.HasPrefix(key, auth.RoleKeyPrefix) { listener.notify(key) } else if trackDocs && !strings.HasPrefix(key, kSyncKeyPrefix) { listener.DocChannel <- event } } } }() return nil }
// Starts a changeListener on a given Bucket. func (listener *changeListener) Start(bucket base.Bucket, trackDocs bool) error { listener.bucket = bucket tapFeed, err := bucket.StartTapFeed(walrus.TapArguments{Backfill: walrus.TapNoBackfill}) if err != nil { return err } listener.tapFeed = tapFeed listener.counter = 1 listener.keyCounts = map[string]uint64{} listener.tapNotifier = sync.NewCond(&sync.Mutex{}) if trackDocs { listener.DocChannel = make(chan walrus.TapEvent, 100) } // Start a goroutine to broadcast to the tapNotifier whenever a channel or user/role changes: go func() { defer func() { listener.notifyStopping() if listener.DocChannel != nil { close(listener.DocChannel) } }() for event := range tapFeed.Events() { if event.Opcode == walrus.TapMutation || event.Opcode == walrus.TapDeletion { key := string(event.Key) if strings.HasPrefix(key, auth.UserKeyPrefix) || strings.HasPrefix(key, auth.RoleKeyPrefix) { if listener.OnDocChanged != nil { listener.OnDocChanged(key, event.Value) } listener.Notify(base.SetOf(key)) } else if trackDocs && !strings.HasPrefix(key, kSyncKeyPrefix) { if listener.OnDocChanged != nil { listener.OnDocChanged(key, event.Value) } listener.DocChannel <- event } } } }() return nil }
// Adds a database to the serverContext given its Bucket. func (sc *serverContext) addDatabase(bucket base.Bucket, dbName string, syncFun *string, nag bool) (*context, error) { if dbName == "" { dbName = bucket.GetName() } if match, _ := regexp.MatchString(`^[a-z][-a-z0-9_$()+/]*$`, dbName); !match { return nil, fmt.Errorf("Illegal database name: %s", dbName) } if sc.databases[dbName] != nil { return nil, fmt.Errorf("Duplicate database name %q", dbName) } dbcontext, err := db.NewDatabaseContext(dbName, bucket) if err != nil { return nil, err } if syncFun != nil { if err := dbcontext.ApplySyncFun(*syncFun); err != nil { return nil, err } } if dbcontext.ChannelMapper == nil { if nag { base.Warn("Sync function undefined; using default") } // Always have a channel mapper object even if it does nothing: dbcontext.ChannelMapper, _ = channels.NewDefaultChannelMapper() } c := &context{ dbcontext: dbcontext, auth: dbcontext.Authenticator(), } sc.databases[dbName] = c return c, nil }
func installViews(bucket base.Bucket) error { // View for finding every Couchbase doc (used when deleting a database) // Key is docid; value is null allbits_map := `function (doc, meta) { emit(meta.id, null); }` // View for _all_docs // Key is docid; value is revid alldocs_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; if (sync.deleted) return; emit(meta.id, sync.rev); }` // View for compaction -- finds all revision docs // Key and value are ignored. oldrevs_map := `function (doc, meta) { var sync = doc._sync; if (meta.id.substring(0,10) == "_sync:rev:") emit("",null); }` // All-principals view // Key is name; value is true for user, false for role principals_map := `function (doc, meta) { var prefix = meta.id.substring(0,11); var isUser = (prefix == %q); if (isUser || prefix == %q) emit(meta.id.substring(%d), isUser); }` principals_map = fmt.Sprintf(principals_map, auth.UserKeyPrefix, auth.RoleKeyPrefix, len(auth.UserKeyPrefix)) // By-channels view. // Key is [channelname, sequence]; value is [docid, revid, flag?] // where flag is true for doc deletion, false for removed from channel, missing otherwise channels_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var sequence = sync.sequence; if (sequence === undefined) return; var value = [meta.id, sync.rev]; if (sync.deleted) value.push(true); emit(["*", sequence], value); var channels = sync.channels; if (channels) { for (var name in channels) { removed = channels[name]; if (!removed) emit([name, sequence], value); else emit([name, removed.seq], [meta.id, removed.rev, !!removed.del, true]); } } }` // Channel access view, used by ComputeChannelsForPrincipal() // Key is username; value is dictionary channelName->firstSequence (compatible with TimedSet) access_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var sequence = sync.sequence; if (sync.deleted || sequence === undefined) return; var access = sync.access; if (access) { for (var name in access) { emit(name, access[name]); } } }` // Role access view, used by ComputeRolesForUser() // Key is username; value is array of role names roleAccess_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var sequence = sync.sequence; if (sync.deleted || sequence === undefined) return; var access = sync.role_access; if (access) { for (var name in access) { emit(name, access[name]); } } }` ddoc := walrus.DesignDoc{ Views: walrus.ViewMap{ "principals": walrus.ViewDef{Map: principals_map}, "channels": walrus.ViewDef{Map: channels_map}, "access": walrus.ViewDef{Map: access_map}, "role_access": walrus.ViewDef{Map: roleAccess_map}, }, } err := bucket.PutDDoc("sync_gateway", ddoc) if err != nil { base.Warn("Error installing Couchbase design doc: %v", err) } ddoc = walrus.DesignDoc{ Views: walrus.ViewMap{ "all_bits": walrus.ViewDef{Map: allbits_map}, "all_docs": walrus.ViewDef{Map: alldocs_map, Reduce: "_count"}, "old_revs": walrus.ViewDef{Map: oldrevs_map, Reduce: "_count"}, }, } err = bucket.PutDDoc("sync_housekeeping", ddoc) if err != nil { base.Warn("Error installing Couchbase design doc: %v", err) } return err }
func installViews(bucket base.Bucket) error { // View for finding every Couchbase doc (used when deleting a database) // Key is docid; value is null allbits_map := `function (doc, meta) { emit(meta.id, null); }` // View for _all_docs // Key is docid; value is [revid, sequence] alldocs_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; if ((sync.flags & 1) || sync.deleted) return; var channels = sync.channels; var channelNames = []; for (ch in channels) { if (channels[ch] == null) channelNames.push(ch); } emit(meta.id, {r:sync.rev, s:sync.sequence, c:channelNames}); }` // View for importing unknown docs // Key is [existing?, docid] where 'existing?' is false for unknown docs import_map := `function (doc, meta) { if(meta.id.substring(0,6) != "_sync:") { var exists = (doc["_sync"] !== undefined); emit([exists, meta.id], null); } }` // View for compaction -- finds all revision docs // Key and value are ignored. oldrevs_map := `function (doc, meta) { var sync = doc._sync; if (meta.id.substring(0,10) == "_sync:rev:") emit("",null); }` // All-principals view // Key is name; value is true for user, false for role principals_map := `function (doc, meta) { var prefix = meta.id.substring(0,11); var isUser = (prefix == %q); if (isUser || prefix == %q) emit(meta.id.substring(%d), isUser); }` principals_map = fmt.Sprintf(principals_map, auth.UserKeyPrefix, auth.RoleKeyPrefix, len(auth.UserKeyPrefix)) // By-channels view. // Key is [channelname, sequence]; value is [docid, revid, flag?] // where flag is true for doc deletion, false for removed from channel, missing otherwise channels_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var sequence = sync.sequence; if (sequence === undefined) return; var value = {rev:sync.rev}; if (sync.flags) { value.flags = sync.flags } else if (sync.deleted) { value.flags = %d // channels.Deleted } if (%v) // EnableStarChannelLog emit(["*", sequence], value); var channels = sync.channels; if (channels) { for (var name in channels) { removed = channels[name]; if (!removed) emit([name, sequence], value); else { var flags = removed.del ? %d : %d; // channels.Removed/Deleted emit([name, removed.seq], {rev:removed.rev, flags: flags}); } } } }` channels_map = fmt.Sprintf(channels_map, channels.Deleted, EnableStarChannelLog, channels.Removed|channels.Deleted, channels.Removed) // Channel access view, used by ComputeChannelsForPrincipal() // Key is username; value is dictionary channelName->firstSequence (compatible with TimedSet) access_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var access = sync.access; if (access) { for (var name in access) { emit(name, access[name]); } } }` // Role access view, used by ComputeRolesForUser() // Key is username; value is array of role names roleAccess_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var access = sync.role_access; if (access) { for (var name in access) { emit(name, access[name]); } } }` ddoc := walrus.DesignDoc{ Views: walrus.ViewMap{ "principals": walrus.ViewDef{Map: principals_map}, "channels": walrus.ViewDef{Map: channels_map}, "access": walrus.ViewDef{Map: access_map}, "role_access": walrus.ViewDef{Map: roleAccess_map}, }, } err := bucket.PutDDoc("sync_gateway", ddoc) if err != nil { base.Warn("Error installing Couchbase design doc: %v", err) } ddoc = walrus.DesignDoc{ Views: walrus.ViewMap{ "all_bits": walrus.ViewDef{Map: allbits_map}, "all_docs": walrus.ViewDef{Map: alldocs_map, Reduce: "_count"}, "import": walrus.ViewDef{Map: import_map, Reduce: "_count"}, "old_revs": walrus.ViewDef{Map: oldrevs_map, Reduce: "_count"}, }, } err = bucket.PutDDoc("sync_housekeeping", ddoc) if err != nil { base.Warn("Error installing Couchbase design doc: %v", err) } return err }
func installViews(bucket base.Bucket) error { // View for finding every Couchbase doc (used when deleting a database) // Key is docid; value is null allbits_map := `function (doc, meta) { emit(meta.id, null); }` // View for _all_docs // Key is docid; value is revid alldocs_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; if (sync.deleted) return; emit(meta.id, sync.rev); }` // By-channels view. // Key is [channelname, sequence]; value is [docid, revid, flag?] // where flag is true for doc deletion, false for removed from channel, missing otherwise channels_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var sequence = sync.sequence; if (sequence === undefined) return; var value = [meta.id, sync.rev]; if (sync.deleted) value.push(true); emit(["*", sequence], value); var channels = sync.channels; if (channels) { for (var name in channels) { removed = channels[name]; if (!removed) emit([name, sequence], value); else emit([name, removed.seq], [meta.id, removed.rev, false]); } } }` // Channel access view, used by ComputeChannelsForPrincipal() // Key is username; value is dictionary channelName->firstSequence access_map := `function (doc, meta) { var sync = doc._sync; if (sync === undefined || meta.id.substring(0,6) == "_sync:") return; var sequence = sync.sequence; if (sync.deleted || sequence === undefined) return; var access = sync.access; if (access) { for (var name in access) { emit(name, access[name]); } } }` ddoc := walrus.DesignDoc{ Views: walrus.ViewMap{ "all_bits": walrus.ViewDef{Map: allbits_map}, "all_docs": walrus.ViewDef{Map: alldocs_map, Reduce: "_count"}, "channels": walrus.ViewDef{Map: channels_map}, "access": walrus.ViewDef{Map: access_map}, }, } err := bucket.PutDDoc("sync_gateway", ddoc) if err != nil { base.Warn("Error installing Couchbase design doc: %v", err) } return err }