Example #1
0
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
}
Example #2
0
// 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
}
Example #3
0
// 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
}
Example #5
0
// 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
}
Example #6
0
// 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
}
Example #7
0
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
}
Example #8
0
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
}
Example #9
0
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
}