text:
#include "lib.h"
#include "array.h"
#include "ioloop.h"
#include "str.h"
#include "llist.h"
#include "mkdir-parents.h"
#include "unlink-directory.h"
#include "index-mail.h"
#include "mail-copy.h"
#include "mail-search.h"
#include "mailbox-list-private.h"
#include "virtual-plugin.h"
#include "virtual-transaction.h"
#include "virtual-storage.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>

#define VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES 64

extern struct mail_storage virtual_storage;
extern struct mailbox virtual_mailbox;
extern struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs;

struct virtual_storage_module virtual_storage_module =
	MODULE_CONTEXT_INIT(&mail_storage_module_register);

static bool ns_is_visible(struct mail_namespace *ns)
{
	return (ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0 ||
		(ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 ||
		(ns->flags & NAMESPACE_FLAG_HIDDEN) == 0;
}

static const char *get_user_visible_mailbox_name(struct mailbox *box)
{
	if (ns_is_visible(box->list->ns))
		return box->vname;
	else {
		return t_strdup_printf("<hidden>%c%s",
				       mail_namespace_get_sep(box->list->ns),
				       box->vname);
	}
}

void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src)
{
	const char *name, *str;
	enum mail_error error;

	name = get_user_visible_mailbox_name(src);
	str = mailbox_get_last_error(src, &error);

	str = t_strdup_printf("%s (for backend mailbox %s)", str, name);
	mail_storage_set_error(dest->storage, error, str);
}

static struct mail_storage *virtual_storage_alloc(void)
{
	struct virtual_storage *storage;
	pool_t pool;

	pool = pool_alloconly_create("virtual storage", 1024);
	storage = p_new(pool, struct virtual_storage, 1);
	storage->storage = virtual_storage;
	storage->storage.pool = pool;
	p_array_init(&storage->open_stack, pool, 8);
	return &storage->storage;
}

static int
virtual_storage_create(struct mail_storage *_storage,
		       struct mail_namespace *ns ATTR_UNUSED,
		       const char **error_r)
{
	struct virtual_storage *storage = (struct virtual_storage *)_storage;
	const char *value;

	value = mail_user_plugin_getenv(_storage->user, "virtual_max_open_mailboxes");
	if (value == NULL)
		storage->max_open_mailboxes = VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES;
	else if (str_to_uint(value, &storage->max_open_mailboxes) < 0) {
		*error_r = "Invalid virtual_max_open_mailboxes setting";
		return -1;
	}
	return 0;
}

static void
virtual_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED,
				  struct mailbox_list_settings *set)
{
	if (set->layout == NULL)
		set->layout = MAILBOX_LIST_NAME_FS;
	if (set->subscription_fname == NULL)
		set->subscription_fname = VIRTUAL_SUBSCRIPTION_FILE_NAME;
}

struct virtual_backend_box *
virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name)
{
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (strcmp(bboxes[i]->name, name) == 0)
			return bboxes[i];
	}
	return NULL;
}

struct virtual_backend_box *
virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id)
{
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;

	if (mailbox_id == 0)
		return NULL;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (bboxes[i]->mailbox_id == mailbox_id)
			return bboxes[i];
	}
	return NULL;
}

static bool virtual_mailbox_is_in_open_stack(struct virtual_storage *storage,
					     const char *name)
{
	const char *const *names;
	unsigned int i, count;

	names = array_get(&storage->open_stack, &count);
	for (i = 0; i < count; i++) {
		if (strcmp(names[i], name) == 0)
			return TRUE;
	}
	return FALSE;
}

static int virtual_backend_box_open_failed(struct virtual_mailbox *mbox,
					   struct virtual_backend_box *bbox)
{
	enum mail_error error;
	const char *str;

	str = t_strdup_printf(
		"Virtual mailbox open failed because of mailbox %s: %s",
		get_user_visible_mailbox_name(bbox->box),
		mailbox_get_last_error(bbox->box, &error));
	mail_storage_set_error(mbox->box.storage, error, str);
	mailbox_free(&bbox->box);

	if (error == MAIL_ERROR_PERM && bbox->wildcard) {
		/* this mailbox wasn't explicitly specified. just skip it. */
		return 0;
	}
	return -1;
}

static int virtual_backend_box_alloc(struct virtual_mailbox *mbox,
				     struct virtual_backend_box *bbox,
				     enum mailbox_flags flags)
{
	struct mail_user *user = mbox->storage->storage.user;
	struct mail_namespace *ns;
	const char *mailbox;
	enum mailbox_existence existence;

	i_assert(bbox->box == NULL);

	if (!bbox->clear_recent)
		flags &= ~MAILBOX_FLAG_DROP_RECENT;

	mailbox = bbox->name;
	ns = mail_namespace_find(user->namespaces, mailbox);
	bbox->box = mailbox_alloc(ns->list, mailbox, flags);

	if (mailbox_exists(bbox->box, TRUE, &existence) < 0)
		return virtual_backend_box_open_failed(mbox, bbox);
	if (existence != MAILBOX_EXISTENCE_SELECT) {
		/* ignore this. it could be intentional. */
		if (mbox->storage->storage.user->mail_debug) {
			i_debug("virtual mailbox %s: "
				"Skipping non-existing mailbox %s",
				mbox->box.vname, bbox->box->vname);
		}
		mailbox_free(&bbox->box);
		return 0;
	}

	i_array_init(&bbox->uids, 64);
	i_array_init(&bbox->sync_pending_removes, 64);
	/* we use modseqs for being able to check quickly if backend mailboxes
	   have changed. make sure the backend has them enabled. */
	mailbox_enable(bbox->box, MAILBOX_FEATURE_CONDSTORE);
	return 1;
}

static int virtual_mailboxes_open(struct virtual_mailbox *mbox,
				  enum mailbox_flags flags)
{
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;
	int ret;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; ) {
		ret = virtual_backend_box_alloc(mbox, bboxes[i], flags);
		if (ret <= 0) {
			if (ret < 0)
				break;
			array_delete(&mbox->backend_boxes, i, 1);
			bboxes = array_get(&mbox->backend_boxes, &count);
		} else {
			i++;
		}
	}
	if (i == count)
		return 0;
	else {
		/* failed */
		for (; i > 0; i--) {
			mailbox_free(&bboxes[i-1]->box);
			array_free(&bboxes[i-1]->uids);
		}
		return -1;
	}
}

static struct mailbox *
virtual_mailbox_alloc(struct mail_storage *_storage, struct mailbox_list *list,
		      const char *vname, enum mailbox_flags flags)
{
	struct virtual_storage *storage = (struct virtual_storage *)_storage;
	struct virtual_mailbox *mbox;
	pool_t pool;

	pool = pool_alloconly_create("virtual mailbox", 2048);
	mbox = p_new(pool, struct virtual_mailbox, 1);
	mbox->box = virtual_mailbox;
	mbox->box.pool = pool;
	mbox->box.storage = _storage;
	mbox->box.list = list;
	mbox->box.mail_vfuncs = &virtual_mail_vfuncs;
	mbox->vfuncs = virtual_mailbox_vfuncs;

	index_storage_mailbox_alloc(&mbox->box, vname, flags, MAIL_INDEX_PREFIX);

	mbox->storage = storage;
	mbox->virtual_ext_id = (uint32_t)-1;
	return &mbox->box;
}

void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox)
{
	struct mailbox_transaction_context *trans;

	if (bbox->sync_mail != NULL) {
		trans = bbox->sync_mail->transaction;
		mail_free(&bbox->sync_mail);
		(void)mailbox_transaction_commit(&trans);
	}
}

static bool virtual_backend_box_can_close(struct virtual_backend_box *bbox)
{
	if (bbox->box->notify_callback != NULL) {
		/* FIXME: IMAP IDLE running - we should support closing this
		   also if mailbox_list_index=yes */
		return FALSE;
	}
	if (array_count(&bbox->sync_pending_removes) > 0) {
		/* FIXME: we could probably close this by making
		   syncing support it? */
		return FALSE;
	}
	return TRUE;
}

static bool
virtual_backend_box_close_any_except(struct virtual_mailbox *mbox,
				     struct virtual_backend_box *except_bbox)
{
	struct virtual_backend_box *bbox;

	/* first try to close a mailbox without any transactions.
	   we'll also skip any mailbox that has notifications enabled (ideally
	   these would be handled by mailbox list index) */
	for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) {
		i_assert(bbox->box->opened);

		if (bbox != except_bbox &&
		    bbox->box->transaction_count == 0 &&
		    virtual_backend_box_can_close(bbox)) {
			i_assert(bbox->sync_mail == NULL);
			virtual_backend_box_close(mbox, bbox);
			return TRUE;
		}
	}

	/* next try to close a mailbox that has sync_mail, but no
	   other transactions */
	for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) {
		if (bbox != except_bbox &&
		    bbox->sync_mail != NULL &&
		    bbox->box->transaction_count == 1 &&
		    virtual_backend_box_can_close(bbox)) {
			virtual_backend_box_sync_mail_unset(bbox);
			i_assert(bbox->box->transaction_count == 0);
			virtual_backend_box_close(mbox, bbox);
			return TRUE;
		}
	}
	return FALSE;
}

void virtual_backend_box_opened(struct virtual_mailbox *mbox,
				struct virtual_backend_box *bbox)
{
	/* the backend mailbox was already opened. if we didn't get here
	   from virtual_backend_box_open() we may need to close a mailbox */
	while (mbox->backends_open_count > mbox->storage->max_open_mailboxes &&
	       virtual_backend_box_close_any_except(mbox, bbox))
		;

	mbox->backends_open_count++;
	DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head,
			    &mbox->open_backend_boxes_tail, bbox,
			    prev_open, next_open);
}

int virtual_backend_box_open(struct virtual_mailbox *mbox,
			     struct virtual_backend_box *bbox)
{
	i_assert(!bbox->box->opened);

	/* try to keep the number of open mailboxes below the threshold
	   before opening the mailbox */
	while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes &&
	       virtual_backend_box_close_any_except(mbox, bbox))
		;

	if (mailbox_open(bbox->box) < 0)
		return -1;
	virtual_backend_box_opened(mbox, bbox);
	return 0;
}

void virtual_backend_box_close(struct virtual_mailbox *mbox,
			       struct virtual_backend_box *bbox)
{
	i_assert(bbox->box->opened);

	if (bbox->search_result != NULL)
		mailbox_search_result_free(&bbox->search_result);

	if (bbox->search_args != NULL &&
	    bbox->search_args_initialized) {
		mail_search_args_deinit(bbox->search_args);
		bbox->search_args_initialized = FALSE;
	}
	i_assert(mbox->backends_open_count > 0);
	mbox->backends_open_count--;

	DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head,
			    &mbox->open_backend_boxes_tail, bbox,
			    prev_open, next_open);
	mailbox_close(bbox->box);
}

void virtual_backend_box_accessed(struct virtual_mailbox *mbox,
				  struct virtual_backend_box *bbox)
{
	DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head,
			    &mbox->open_backend_boxes_tail, bbox,
			    prev_open, next_open);
	DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head,
			    &mbox->open_backend_boxes_tail, bbox,
			    prev_open, next_open);
}

static void virtual_mailbox_close_internal(struct virtual_mailbox *mbox)
{
	struct virtual_backend_box **bboxes;
	unsigned int i, count;

	bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (bboxes[i]->box == NULL)
			continue;

		if (bboxes[i]->box->opened)
			virtual_backend_box_close(mbox, bboxes[i]);
		mailbox_free(&bboxes[i]->box);
		if (array_is_created(&bboxes[i]->sync_outside_expunges))
			array_free(&bboxes[i]->sync_outside_expunges);
		array_free(&bboxes[i]->sync_pending_removes);
		array_free(&bboxes[i]->uids);
	}
	i_assert(mbox->backends_open_count == 0);
}

static int
virtual_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED,
		       enum mailbox_existence *existence_r)
{
	return index_storage_mailbox_exists_full(box, VIRTUAL_CONFIG_FNAME,
						 existence_r);
}

static int virtual_mailbox_open(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	bool broken;
	int ret = 0;

	if (virtual_mailbox_is_in_open_stack(mbox->storage, box->name)) {
		mail_storage_set_critical(box->storage,
			"Virtual mailbox loops: %s", box->name);
		return -1;
	}

	if (!array_is_created(&mbox->backend_boxes))
		ret = virtual_config_read(mbox);
	if (ret == 0) {
		array_append(&mbox->storage->open_stack, &box->name, 1);
		ret = virtual_mailboxes_open(mbox, box->flags);
		array_delete(&mbox->storage->open_stack,
			     array_count(&mbox->storage->open_stack)-1, 1);
	}
	if (ret < 0) {
		virtual_mailbox_close_internal(mbox);
		return -1;
	}
	if (index_storage_mailbox_open(box, FALSE) < 0)
		return -1;

	mbox->virtual_ext_id =
		mail_index_ext_register(mbox->box.index, "virtual", 0,
			sizeof(struct virtual_mail_index_record),
			sizeof(uint32_t));

	if (virtual_mailbox_ext_header_read(mbox, box->view, &broken) < 0) {
		virtual_mailbox_close_internal(mbox);
		index_storage_mailbox_close(box);
		return -1;
	}
	return 0;
}

static void virtual_mailbox_close(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	virtual_mailbox_close_internal(mbox);
	index_storage_mailbox_close(box);
}

static void virtual_mailbox_free(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	virtual_config_free(mbox);
	index_storage_mailbox_free(box);
}

static int
virtual_mailbox_create(struct mailbox *box,
		       const struct mailbox_update *update ATTR_UNUSED,
		       bool directory ATTR_UNUSED)
{
	mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
			       "Can't create virtual mailboxes");
	return -1;
}

static int
virtual_mailbox_update(struct mailbox *box,
		       const struct mailbox_update *update ATTR_UNUSED)
{
	mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
			       "Can't update virtual mailboxes");
	return -1;
}

static int virtual_storage_set_have_guid_flags(struct virtual_mailbox *mbox)
{
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;
	struct mailbox_status status;
	bool opened;

	mbox->have_guids = TRUE;
	mbox->have_save_guids = TRUE;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		opened = bboxes[i]->box->opened;
		if (mailbox_get_status(bboxes[i]->box, 0, &status) < 0) {
			virtual_box_copy_error(&mbox->box, bboxes[i]->box);
			return -1;
		}
		i_assert(bboxes[i]->box->opened == opened);
		if (!status.have_guids)
			mbox->have_guids = FALSE;
		if (!status.have_save_guids)
			mbox->have_save_guids = FALSE;
	}
	return 0;
}

static int
virtual_storage_get_status(struct mailbox *box,
			   enum mailbox_status_items items,
			   struct mailbox_status *status_r)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	if ((items & STATUS_LAST_CACHED_SEQ) != 0)
		items |= STATUS_MESSAGES;

	if (index_storage_get_status(box, items, status_r) < 0)
		return -1;

	if ((items & STATUS_LAST_CACHED_SEQ) != 0) {
		/* Virtual mailboxes have no cached data of their own, so the
		   current value is always 0. The most important use for this
		   functionality is for "doveadm index" to do FTS indexing and
		   it doesn't really matter there if we set this value
		   correctly or not. So for now just assume that everything is
		   indexed. */
		status_r->last_cached_seq = status_r->messages;
	}
	if (!mbox->have_guid_flags_set) {
		if (virtual_storage_set_have_guid_flags(mbox) < 0)
			return -1;
		mbox->have_guid_flags_set = TRUE;
	}

	if (mbox->have_guids)
		status_r->have_guids = TRUE;
	if (mbox->have_save_guids)
		status_r->have_save_guids = TRUE;
	return 0;
}

static int
virtual_mailbox_get_metadata(struct mailbox *box,
			     enum mailbox_metadata_items items,
			     struct mailbox_metadata *metadata_r)
{
	if (index_mailbox_get_metadata(box, items, metadata_r) < 0)
		return -1;
	if ((items & MAILBOX_METADATA_GUID) != 0) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
				       "Virtual mailboxes have no GUIDs");
		return -1;
	}
	return 0;
}

static void
virtual_notify_callback(struct mailbox *bbox ATTR_UNUSED, struct mailbox *box)
{
	box->notify_callback(box, box->notify_context);
}

static void virtual_notify_changes(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	struct virtual_backend_box *const *bboxp;

	if (box->notify_callback == NULL) {
		array_foreach(&mbox->backend_boxes, bboxp)
			mailbox_notify_changes_stop((*bboxp)->box);
		return;
	}

	/* FIXME: if mailbox_list_index=yes, use mailbox-list-notify.h API
	   to wait for changes and avoid opening all mailboxes here. */

	array_foreach(&mbox->backend_boxes, bboxp) {
		if (!(*bboxp)->box->opened &&
		    virtual_backend_box_open(mbox, *bboxp) < 0) {
			/* we can't report error in here, so do it later */
			(*bboxp)->open_failed = TRUE;
			continue;
		}
		mailbox_notify_changes((*bboxp)->box,
				       virtual_notify_callback, box);
	}
}

static void
virtual_get_virtual_uids(struct mailbox *box,
			 struct mailbox *backend_mailbox,
			 const ARRAY_TYPE(seq_range) *backend_uids,
			 ARRAY_TYPE(seq_range) *virtual_uids_r)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	struct virtual_backend_box *bbox;
	const struct virtual_backend_uidmap *uids;
	struct seq_range_iter iter;
	unsigned int n, i, count;
	uint32_t uid;

	if (mbox->lookup_prev_bbox != NULL &&
	    strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0)
		bbox = mbox->lookup_prev_bbox;
	else {
		bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname);
		mbox->lookup_prev_bbox = bbox;
	}
	if (bbox == NULL)
		return;

	uids = array_get(&bbox->uids, &count); i = 0;
	seq_range_array_iter_init(&iter, backend_uids); n = 0;
	while (seq_range_array_iter_nth(&iter, n++, &uid)) {
		while (i < count && uids[i].real_uid < uid) i++;
		if (i < count && uids[i].real_uid == uid) {
			seq_range_array_add(virtual_uids_r,
					    uids[i].virtual_uid);
			i++;
		}
	}
}

static void
virtual_get_virtual_uid_map(struct mailbox *box,
			    struct mailbox *backend_mailbox,
			    const ARRAY_TYPE(seq_range) *backend_uids,
			    ARRAY_TYPE(uint32_t) *virtual_uids_r)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	struct virtual_backend_box *bbox;
	const struct virtual_backend_uidmap *uids;
	struct seq_range_iter iter;
	unsigned int n, i, count;
	uint32_t uid;

	if (mbox->lookup_prev_bbox != NULL &&
	    strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0)
		bbox = mbox->lookup_prev_bbox;
	else {
		bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname);
		mbox->lookup_prev_bbox = bbox;
	}
	if (bbox == NULL)
		return;

	uids = array_get(&bbox->uids, &count); i = 0;
	seq_range_array_iter_init(&iter, backend_uids); n = 0;
	while (seq_range_array_iter_nth(&iter, n++, &uid)) {
		while (i < count && uids[i].real_uid < uid) i++;
		if (i == count || uids[i].real_uid > uid) {
			uint32_t zero = 0;

			array_append(virtual_uids_r, &zero, 1);
		} else {
			array_append(virtual_uids_r, &uids[i].virtual_uid, 1);
			i++;
		}
	}
}

static void
virtual_get_virtual_backend_boxes(struct mailbox *box,
				  ARRAY_TYPE(mailboxes) *mailboxes,
				  bool only_with_msgs)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
	struct virtual_backend_box *const *bboxes;
	unsigned int i, count;

	bboxes = array_get(&mbox->backend_boxes, &count);
	for (i = 0; i < count; i++) {
		if (!only_with_msgs || array_count(&bboxes[i]->uids) > 0)
			array_append(mailboxes, &bboxes[i]->box, 1);
	}
}

static bool virtual_is_inconsistent(struct mailbox *box)
{
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;

	if (mbox->inconsistent)
		return TRUE;

	return index_storage_is_inconsistent(box);
}

struct mail_storage virtual_storage = {
	.name = VIRTUAL_STORAGE_NAME,
	.class_flags = MAIL_STORAGE_CLASS_FLAG_NOQUOTA,

	.v = {
		NULL,
		virtual_storage_alloc,
		virtual_storage_create,
		index_storage_destroy,
		NULL,
		virtual_storage_get_list_settings,
		NULL,
		virtual_mailbox_alloc,
		NULL
	}
};

struct mailbox virtual_mailbox = {
	.v = {
		index_storage_is_readonly,
		index_storage_mailbox_enable,
		virtual_mailbox_exists,
		virtual_mailbox_open,
		virtual_mailbox_close,
		virtual_mailbox_free,
		virtual_mailbox_create,
		virtual_mailbox_update,
		index_storage_mailbox_delete,
		index_storage_mailbox_rename,
		virtual_storage_get_status,
		virtual_mailbox_get_metadata,
		index_storage_set_subscribed,
		index_storage_attribute_set,
		index_storage_attribute_get,
		index_storage_attribute_iter_init,
		index_storage_attribute_iter_next,
		index_storage_attribute_iter_deinit,
		NULL,
		NULL,
		virtual_storage_sync_init,
		index_mailbox_sync_next,
		index_mailbox_sync_deinit,
		NULL,
		virtual_notify_changes,
		virtual_transaction_begin,
		virtual_transaction_commit,
		virtual_transaction_rollback,
		NULL,
		virtual_mail_alloc,
		virtual_search_init,
		virtual_search_deinit,
		virtual_search_next_nonblock,
		virtual_search_next_update_seq,
		virtual_save_alloc,
		virtual_save_begin,
		virtual_save_continue,
		virtual_save_finish,
		virtual_save_cancel,
		mail_storage_copy,
		NULL,
		NULL,
		NULL,
		virtual_is_inconsistent
	}
};

struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs = {
	virtual_get_virtual_uids,
	virtual_get_virtual_uid_map,
	virtual_get_virtual_backend_boxes
};


text: <manifest package="me.jfenn.colorpickerdialog.imagepicker" />

redpajama_set_name: RedPajamaGithub


text:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Reflection;

[CustomEditor(typeof(HealthBar))]
[CanEditMultipleObjects]
public class _levelSelectionLogic : Editor
{
	bool showSettings = true;
	bool healthLink = false;
    bool alphaSettings = true;
    bool hitSettings = true;

	public override void OnInspectorGUI()
	{
		HealthBar myTarget = (HealthBar)target;
        Undo.RecordObject(target, "Undo");

		EditorGUILayout.BeginVertical("Box");
			GUILayout.Space(5);
			EditorGUILayout.BeginHorizontal ();
			GUILayout.Space (10);
			healthLink = EditorGUILayout.Foldout(healthLink,"Health Link");
			GUILayout.FlexibleSpace();
			EditorGUILayout.EndHorizontal ();
			if(healthLink)
			{
			EditorGUILayout.BeginHorizontal ();
			GUILayout.Space(5);
				EditorGUILayout.BeginVertical("Box");
					myTarget.healthLink.targetScript = (MonoBehaviour)EditorGUILayout.ObjectField ("Health Script", myTarget.healthLink.targetScript, typeof(MonoBehaviour), true);
					myTarget.healthLink.fieldName = EditorGUILayout.TextField ("Health var name", myTarget.healthLink.fieldName);
				EditorGUILayout.EndVertical();
			EditorGUILayout.EndHorizontal ();
			}
			myTarget.HealthbarPrefab = (RectTransform)EditorGUILayout.ObjectField ("HealthbarPrefab",myTarget.HealthbarPrefab, typeof(RectTransform), false);
			myTarget.yOffset = EditorGUILayout.FloatField ("Y Offset", myTarget.yOffset);

			EditorGUILayout.BeginHorizontal ();
				GUILayout.Space (10);
				showSettings = EditorGUILayout.Foldout(showSettings,"Other Settings");
				GUILayout.FlexibleSpace();
			EditorGUILayout.EndHorizontal ();

			if(showSettings)
			{
				EditorGUILayout.BeginVertical("Box");
					GUILayout.Space(5);

					myTarget.keepSize = EditorGUILayout.Toggle("Fixed Size", myTarget.keepSize);
					GUILayout.Space(5);

					myTarget.scale = EditorGUILayout.FloatField ("Scale", myTarget.scale);
					myTarget.sizeOffsets = EditorGUILayout.Vector2Field("Size Offsets", myTarget.sizeOffsets);

					EditorGUILayout.BeginHorizontal ();
						myTarget.DrawOFFDistance = EditorGUILayout.ToggleLeft("Draw Distance", myTarget.DrawOFFDistance, GUILayout.Width(100));
						GUILayout.FlexibleSpace();
						if(myTarget.DrawOFFDistance)
							myTarget.drawDistance = EditorGUILayout.FloatField("", myTarget.drawDistance, GUILayout.Width(100));
					EditorGUILayout.EndHorizontal ();
					GUILayout.Space(5);

					myTarget.showHealthInfo = EditorGUILayout.ToggleLeft("Health Info", myTarget.showHealthInfo, GUILayout.Width(100));

					if(myTarget.showHealthInfo)
					{
						myTarget.healthInfoAlignment = (HealthBar.HealthInfoAlignment)EditorGUILayout.EnumPopup("Alignment", myTarget.healthInfoAlignment);
						myTarget.healthInfoSize = EditorGUILayout.FloatField("Size Factor", myTarget.healthInfoSize);
					}
                    EditorGUILayout.BeginHorizontal();
                    GUILayout.Space(10);
                        EditorGUILayout.BeginVertical();
                        alphaSettings = EditorGUILayout.Foldout(alphaSettings, "Alpha Settings");
                        if (alphaSettings)
                        {
                            EditorGUILayout.HelpBox("States alphas and it's fade speeds. Zero is no fade.", MessageType.Info);
                            EditorGUILayout.BeginHorizontal();
                            EditorGUIUtility.labelWidth = 45;
                            EditorGUIUtility.fieldWidth = 45;
                            myTarget.alphaSettings.defaultAlpha = EditorGUILayout.Slider("Default", myTarget.alphaSettings.defaultAlpha, 0, 1);
                            EditorGUIUtility.labelWidth = 75;
                            myTarget.alphaSettings.defaultFadeSpeed = EditorGUILayout.FloatField("Fade Speed", myTarget.alphaSettings.defaultFadeSpeed);
                            EditorGUILayout.EndHorizontal();

                            EditorGUILayout.BeginHorizontal();
                            EditorGUIUtility.labelWidth = 45;
                            EditorGUIUtility.fieldWidth = 45;
                            myTarget.alphaSettings.fullAplpha = EditorGUILayout.Slider("Full ", myTarget.alphaSettings.fullAplpha, 0, 1);
                            EditorGUIUtility.labelWidth = 75;
                            myTarget.alphaSettings.fullFadeSpeed = EditorGUILayout.FloatField("Fade Speed", myTarget.alphaSettings.fullFadeSpeed);
                            EditorGUILayout.EndHorizontal();

                            EditorGUILayout.BeginHorizontal();
                            EditorGUIUtility.labelWidth = 45;
                            EditorGUIUtility.fieldWidth = 45;
                            myTarget.alphaSettings.nullAlpha = EditorGUILayout.Slider("Null ", myTarget.alphaSettings.nullAlpha, 0, 1);
                            EditorGUIUtility.labelWidth = 75;
                            myTarget.alphaSettings.nullFadeSpeed = EditorGUILayout.FloatField("Fade Speed", myTarget.alphaSettings.nullFadeSpeed);
                            EditorGUILayout.EndHorizontal();

                            EditorGUILayout.BeginHorizontal();
                            GUILayout.Space(10);
                            EditorGUILayout.BeginVertical();
                            hitSettings = EditorGUILayout.Foldout(hitSettings, "Hit Settings");
                            if (hitSettings)
                            {
                                myTarget.alphaSettings.onHit.onHitAlpha = EditorGUILayout.Slider("On Hit Alpha", myTarget.alphaSettings.onHit.onHitAlpha, 0, 1);
                                myTarget.alphaSettings.onHit.fadeSpeed = EditorGUILayout.FloatField("Fade Speed", myTarget.alphaSettings.onHit.fadeSpeed);
                                myTarget.alphaSettings.onHit.duration = EditorGUILayout.FloatField("Duration", myTarget.alphaSettings.onHit.duration);
                            }
                            EditorGUILayout.EndVertical();
                            EditorGUILayout.EndHorizontal();
                        }
                        EditorGUILayout.EndVertical();
                    EditorGUILayout.EndHorizontal();

				EditorGUILayout.EndVertical();
			}
		EditorGUILayout.EndVertical();

		if (GUI.changed)
			EditorUtility.SetDirty(myTarget);
	}
}


text: include_recipe "cumulus-linux-test::test_ports.rb"


text:
require 'pagify/helper/html'

module Pagify
  module Helper
    module Web
      def pagify_links objs, opts
        html = objs.pager.html
        name = html.setting[:query_name]
        type = html.setting[:links_type]
        base = opts[:path].call
        "
" +
          html.send(type, opts[:page][name]){ |p| base + "?#{name}=#{p}" } +
        '
'
      end
    end
  end
end


text:
import { createStore } from 'redux';

// action generator functions: that generate(return) action objects
// (action object: obj that gets sent to the store)

// (note: using  implicit object return syntax)
// (provide default value if passed arg undefined)
// NOTE: destructure arguments that get passed dinto functions
const incrementCount = ({ incrementBy = 1 } = {}) => ({
    type: 'INCREMENT',
    //const incrementBy = typeof payload.incrementBy === 'number' ? payload.incrementBy : 1;
    incrementBy
  });

  const decrementCount = ({ decrementBy = 1 } = {}) => ({
    type: 'DECREMENT',
    decrementBy
  });

  const setCount = ({ count }) => ({
    type: 'SET',
    count
  });

  const resetCount = () => ({
    type: 'RESET'
  });

// Reducer Function: Action describe the fact that
// something happened but dont specify how app changes
// in response. This is the job of reducers

// reducers:
// 1. reducers are pure functions. output only determined by the input
// (doesnt use or change anything outside of the function scope)
// 2. never change state or action
const countReducer = (state = { count: 0 }, action) => {
    switch(action.type) {
        case 'INCREMENT':
            //const incrementBy = typeof action.incrementBy === 'number' ? action.incrementBy : 1;
            return {
                count: state.count + action.incrementBy
            };
        case 'DECREMENT':
            //const decrementBy = typeof action.decrementBy === 'number' ? action.decrementBy : 1;
            return {
                count: state.count - action.decrementBy
            };
        case 'SET':
            return {
                count: action.count
            };
        case 'RESET':
            return {
                count: 0
            };
        default:
            return state;

    }

};

// pass function in, which gets called once right away
const store = createStore(countReducer);

// called everytime store changes
const unsubscribe = store.subscribe(() => {
    console.log(store.getState(store.getState()));
})

store.dispatch(incrementCount({ incrementBy: 5 }))

store.dispatch(incrementCount());

store.dispatch(resetCount());

store.dispatch(decrementCount());

store.dispatch(decrementCount({ decrementBy: 3 }));

store.dispatch(setCount({ count: 250 }));


text:
<?php
/**
 * Created by PhpStorm.
 * User: hebidu
 * Date: 2017-10-17
 * Time: 10:38
 */

namespace sunsun\server\resp;

use sunsun\po\BaseRespPo;

/**
 * 设备端响应的设备信息包
 * Class BaseDeviceInfoClientResp
 * @package sunsun\server\resp
 */
abstract class BaseDeviceInfoClientResp extends BaseRespPo
{

}


text:
**CD Simple**

The purpose is to show a BEST PRACTICE implementation of a Core Data stack.

Features:
- uses the simple "Event" entity that comes with a templated project
- Core Data objects are in a CDStack class
- Model class remains responsible for the app's data
- app delegate is clean

High-level design philosophy:
- a model object will manage the app's state
- the Core Data "stack" will be managed by a dedicated object (CDStack)

This app replicates the functionality of the Xcode template project, but does it the "right" way.

Here's how this app was built:

We started with an Empty Application, named "Classes".

We did NOT check/mark the "Use Core Data" checkbox.
We will add the Core Data components manually...
In the Project navigator, click to select your project file. In the details panel, the "Summary" info should appear.
Click the "Build Phases" tab, and spin open the "Link Library With Libraries" section.
Add the CoreData.framework. Click-and-drag it to the bottom of the list of libraries.
Finally, in the app's pch file, add: #import <CoreData/CoreData.h>

In the "Classes" folder/group, add a Core Data "Data Model" object, and call it "ObjectModel" (it will add the xcdatamodeld extension).

Create a Model class (later we'll save one as a template and copy that in).
Add it (i.e. #import...) to the app's pch file.

Copy in the CDStack class (from this app; "Add Files to <projectname>...").

Add (create) Main.storyboard. On the project's "General" settings panel, set the "Main Interface" dropdown to this just-added storyboard. Finally, in the app delegate's application:didFinishLaunchingWithOptions: method, return the code on lines 15 through 18. In other words, simply return YES from the method.

Now you can add/create controllers to manage the scenes (one per "view controller" scene).
A best practice is to use names in the form <Entity><VCtype>.
<Entity> is a data entity, or a business logic operation.
<VCstyle> is the style of view controller (e.g. List, Detail, Edit).
So, for this app, the names are...
EventList - a list of "event" objects
EventDetail - the details for a specific "event" object

First, a navigation controller is added, which creates a table view with it.
Its table view cell settings get updated (Basic, identifier is "Cell").
Then, a view controller is added. Next, a segue gets added, from the table view cell to the view controller.
Finally, the navigation controller is marked as the initial scene.

For each view controller scene, set its identity on the Identity inspector.

Then, design the entity (or entities) for the app, by using either the table-mode or graphical-mode designer.
Click/select the Core Data "ObjectModel" item, and work with it.

See the remaining code for best practices.


text:
package io.apiman.gateway.engine.metrics;

import io.apiman.gateway.engine.IMetrics;

import java.io.Serializable;
import java.util.Date;

/**
 * Metric information about a single request processed by the API Gateway.
 * This information is then reported to the {@link IMetrics} subsystem.
 *
 * @author eric.wittmann@redhat.com
 */
public class RequestMetric implements Serializable {

    private static final long serialVersionUID = 7085676761317470403L;

    private Date requestStart;
    private Date requestEnd;
    private long requestDuration = -1;
    private Date serviceStart;
    private Date serviceEnd;
    private long serviceDuration = 1;
    private String resource;
    private String method;
    private String serviceOrgId;
    private String serviceId;
    private String serviceVersion;
    private String planId;
    private String applicationOrgId;
    private String applicationId;
    private String applicationVersion;
    private String contractId;
    private String user;
    private int responseCode;
    private String responseMessage;
    private boolean failure;
    private int failureCode;
    private String failureReason;
    private boolean error;
    private String errorMessage;

    /**
     * Constructor.
     */
    public RequestMetric() {
    }

    /**
     * @return the serviceDuration
     */
    public long getServiceDuration() {
        return serviceDuration;
    }

    /**
     * @param serviceDuration the serviceDuration to set
     */
    public void setServiceDuration(long serviceDuration) {
        this.serviceDuration = serviceDuration;
    }

    /**
     * @return the resource
     */
    public String getResource() {
        return resource;
    }

    /**
     * @param resource the resource to set
     */
    public void setResource(String resource) {
        this.resource = resource;
    }

    /**
     * @return the method
     */
    public String getMethod() {
        return method;
    }

    /**
     * @param method the method to set
     */
    public void setMethod(String method) {
        this.method = method;
    }

    /**
     * @return the serviceOrgId
     */
    public String getServiceOrgId() {
        return serviceOrgId;
    }

    /**
     * @param serviceOrgId the serviceOrgId to set
     */
    public void setServiceOrgId(String serviceOrgId) {
        this.serviceOrgId = serviceOrgId;
    }

    /**
     * @return the serviceId
     */
    public String getServiceId() {
        return serviceId;
    }

    /**
     * @param serviceId the serviceId to set
     */
    public void setServiceId(String serviceId) {
        this.serviceId = serviceId;
    }

    /**
     * @return the serviceVersion
     */
    public String getServiceVersion() {
        return serviceVersion;
    }

    /**
     * @param serviceVersion the serviceVersion to set
     */
    public void setServiceVersion(String serviceVersion) {
        this.serviceVersion = serviceVersion;
    }

    /**
     * @return the applicationOrgId
     */
    public String getApplicationOrgId() {
        return applicationOrgId;
    }

    /**
     * @param applicationOrgId the applicationOrgId to set
     */
    public void setApplicationOrgId(String applicationOrgId) {
        this.applicationOrgId = applicationOrgId;
    }

    /**
     * @return the applicationId
     */
    public String getApplicationId() {
        return applicationId;
    }

    /**
     * @param applicationId the applicationId to set
     */
    public void setApplicationId(String applicationId) {
        this.applicationId = applicationId;
    }

    /**
     * @return the applicationVersion
     */
    public String getApplicationVersion() {
        return applicationVersion;
    }

    /**
     * @param applicationVersion the applicationVersion to set
     */
    public void setApplicationVersion(String applicationVersion) {
        this.applicationVersion = applicationVersion;
    }

    /**
     * @return the contractId
     */
    public String getContractId() {
        return contractId;
    }

    /**
     * @param contractId the contractId to set
     */
    public void setContractId(String contractId) {
        this.contractId = contractId;
    }

    /**
     * @return the responseCode
     */
    public int getResponseCode() {
        return responseCode;
    }

    /**
     * @param responseCode the responseCode to set
     */
    public void setResponseCode(int responseCode) {
        this.responseCode = responseCode;
    }

    /**
     * @return the responseMessage
     */
    public String getResponseMessage() {
        return responseMessage;
    }

    /**
     * @param responseMessage the responseMessage to set
     */
    public void setResponseMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }

    /**
     * @return the failureCode
     */
    public int getFailureCode() {
        return failureCode;
    }

    /**
     * @param failureCode the failureCode to set
     */
    public void setFailureCode(int failureCode) {
        this.failureCode = failureCode;
    }

    /**
     * @return the failureReason
     */
    public String getFailureReason() {
        return failureReason;
    }

    /**
     * @param failureReason the failureReason to set
     */
    public void setFailureReason(String failureReason) {
        this.failureReason = failureReason;
    }

    /**
     * @return the requestStart
     */
    public Date getRequestStart() {
        return requestStart;
    }

    /**
     * @param requestStart the requestStart to set
     */
    public void setRequestStart(Date requestStart) {
        this.requestStart = requestStart;
    }

    /**
     * @return the requestEnd
     */
    public Date getRequestEnd() {
        return requestEnd;
    }

    /**
     * @param requestEnd the requestEnd to set
     */
    public void setRequestEnd(Date requestEnd) {
        this.requestEnd = requestEnd;
        this.requestDuration = requestEnd.getTime() - requestStart.getTime();
    }

    /**
     * @return the requestDuration
     */
    public long getRequestDuration() {
        return requestDuration;
    }

    /**
     * @param requestDuration the requestDuration to set
     */
    public void setRequestDuration(long requestDuration) {
        this.requestDuration = requestDuration;
    }

    /**
     * @return the serviceStart
     */
    public Date getServiceStart() {
        return serviceStart;
    }

    /**
     * @param serviceStart the serviceStart to set
     */
    public void setServiceStart(Date serviceStart) {
        this.serviceStart = serviceStart;
    }

    /**
     * @return the serviceEnd
     */
    public Date getServiceEnd() {
        return serviceEnd;
    }

    /**
     * @param serviceEnd the serviceEnd to set
     */
    public void setServiceEnd(Date serviceEnd) {
        this.serviceEnd = serviceEnd;
        this.serviceDuration = serviceEnd.getTime() - serviceStart.getTime();
    }

    /**
     * @return the error
     */
    public boolean isError() {
        return error;
    }

    /**
     * @param error the error to set
     */
    public void setError(boolean error) {
        this.error = error;
    }

    /**
     * @return the errorMessage
     */
    public String getErrorMessage() {
        return errorMessage;
    }

    /**
     * @param errorMessage the errorMessage to set
     */
    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    /**
     * @return the failure
     */
    public boolean isFailure() {
        return failure;
    }

    /**
     * @param failure the failure to set
     */
    public void setFailure(boolean failure) {
        this.failure = failure;
    }

    /**
     * @return the planId
     */
    public String getPlanId() {
        return planId;
    }

    /**
     * @param planId the planId to set
     */
    public void setPlanId(String planId) {
        this.planId = planId;
    }

    /**
     * @return the user
     */
    public String getUser() {
        return user;
    }

    /**
     * @param user the user to set
     */
    public void setUser(String user) {
        this.user = user;
    }

}


text:
'use strict';

var is = require('bpmn-js/lib/util/ModelUtil').is,
    entryFactory = require('../../../factory/EntryFactory'),
    participantHelper = require('../../../helper/ParticipantHelper'),
    getBusinessObject = require('bpmn-js/lib/util/ModelUtil').getBusinessObject,
    nameEntryFactory = require('./implementation/Name');

module.exports = function(group, element) {
  var businessObject = getBusinessObject(element);

  if (is(element, 'bpmn:Process') || (is(element, 'bpmn:Participant') && businessObject.get('processRef'))) {

    /**
     * processId
     */
    if (is(element, 'bpmn:Participant')) {
      var idEntry = entryFactory.textField({
        id: 'process-id',
        description: '',
        label: 'Process Id',
        modelProperty: 'processId'
      });

      // in participants we have to change the default behavior of set and get
      idEntry.get = function(element) {
        var properties = participantHelper.getProcessBusinessObject(element, 'id');
        return { processId: properties.id };
      };

      idEntry.set = function(element, values) {
        return participantHelper.modifyProcessBusinessObject(element, 'id', { id: values.processId });
      };

      group.entries.push(idEntry);

      /**
       * process name
       */
      var processNameEntry = nameEntryFactory(element, {
        id: 'process-name',
        label: 'Process Name'
      })[0];

      // in participants we have to change the default behavior of set and get
      processNameEntry.get = function(element) {
        return participantHelper.getProcessBusinessObject(element, 'name');
      };

      processNameEntry.set = function(element, values) {
        return participantHelper.modifyProcessBusinessObject(element, 'name', values);
      };

      group.entries.push(processNameEntry);
    }

    /**
     * isExecutable
     */
    var executableEntry = entryFactory.checkbox({
      id: 'process-is-executable',
      description: 'Defines if a process is executable by a process engine',
      label: 'Executable',
      modelProperty: 'isExecutable'
    });

    // in participants we have to change the default behavior of set and get
    if (is(element, 'bpmn:Participant')) {
      executableEntry.get = function(element) {
        return participantHelper.getProcessBusinessObject(element, 'isExecutable');
      };

      executableEntry.set = function(element, values) {
        return participantHelper.modifyProcessBusinessObject(element, 'isExecutable', values);
      };
    }

    group.entries.push(executableEntry);
  }
};


text:
// # Base Model
// This is the model from which all other Ghost models extend. The model is based on Bookshelf.Model, and provides
// several basic behaviours such as UUIDs, as well as a set of Data methods for accessing information from the database.
//
// The models are internal to Ghost, only the API and some internal functions such as migration and import/export
// accesses the models directly. All other parts of Ghost, including the blog frontend, admin UI, and apps are only
// allowed to access data via the API.
const _ = require('lodash'),
    bookshelf = require('bookshelf'),
    moment = require('moment'),
    Promise = require('bluebird'),
    gql = require('ghost-gql'),
    ObjectId = require('bson-objectid'),
    debug = require('ghost-ignition').debug('models:base'),
    config = require('../../config'),
    db = require('../../data/db'),
    common = require('../../lib/common'),
    security = require('../../lib/security'),
    filters = require('../../filters'),
    schema = require('../../data/schema'),
    urlService = require('../../services/url'),
    validation = require('../../data/validation'),
    plugins = require('../plugins');

let ghostBookshelf,
    proto;

// ### ghostBookshelf
// Initializes a new Bookshelf instance called ghostBookshelf, for reference elsewhere in Ghost.
ghostBookshelf = bookshelf(db.knex);

// Load the Bookshelf registry plugin, which helps us avoid circular dependencies
ghostBookshelf.plugin('registry');

// Add committed/rollback events.
ghostBookshelf.plugin(plugins.transactionEvents);

// Load the Ghost filter plugin, which handles applying a 'filter' to findPage requests
ghostBookshelf.plugin(plugins.filter);

// Load the Ghost include count plugin, which allows for the inclusion of cross-table counts
ghostBookshelf.plugin(plugins.includeCount);

// Load the Ghost pagination plugin, which gives us the `fetchPage` method on Models
ghostBookshelf.plugin(plugins.pagination);

// Update collision plugin
ghostBookshelf.plugin(plugins.collision);

// Manages nested updates (relationships)
ghostBookshelf.plugin('bookshelf-relations', {
    allowedOptions: ['context', 'importing', 'migrating'],
    unsetRelations: true,
    hooks: {
        belongsToMany: {
            after: function (existing, targets, options) {
                // reorder tags/authors
                var queryOptions = {
                    query: {
                        where: {}
                    }
                };

                // CASE: disable after hook for specific relations
                if (['permissions_roles'].indexOf(existing.relatedData.joinTableName) !== -1) {
                    return Promise.resolve();
                }

                return Promise.each(targets.models, function (target, index) {
                    queryOptions.query.where[existing.relatedData.otherKey] = target.id;

                    return existing.updatePivot({
                        sort_order: index
                    }, _.extend({}, options, queryOptions));
                });
            },
            beforeRelationCreation: function onCreatingRelation(model, data) {
                data.id = ObjectId.generate();
            }
        }
    }
});

// Cache an instance of the base model prototype
proto = ghostBookshelf.Model.prototype;

// ## ghostBookshelf.Model
// The Base Model which other Ghost objects will inherit from,
// including some convenience functions as static properties on the model.
ghostBookshelf.Model = ghostBookshelf.Model.extend({
    // Bookshelf `hasTimestamps` - handles created_at and updated_at properties
    hasTimestamps: true,

    // https://github.com/bookshelf/bookshelf/commit/a55db61feb8ad5911adb4f8c3b3d2a97a45bd6db
    parsedIdAttribute: function () {
        return false;
    },

    // Ghost option handling - get permitted attributes from server/data/schema.js, where the DB schema is defined
    permittedAttributes: function permittedAttributes() {
        return _.keys(schema.tables[this.tableName]);
    },

    // When loading an instance, subclasses can specify default to fetch
    defaultColumnsToFetch: function defaultColumnsToFetch() {
        return [];
    },

    /**
     * @NOTE
     * We have to remember the `_previousAttributes` attributes, because when destroying resources
     * We listen on the `onDestroyed` event and Bookshelf resets these properties right after the event.
     * If the query runs in a txn, `_previousAttributes` will be empty.
     */
    emitChange: function (model, event, options) {
        debug(model.tableName, event);

        const previousAttributes = model._previousAttributes;

        if (!options.transacting) {
            return common.events.emit(event, model, options);
        }

        if (!model.ghostEvents) {
            model.ghostEvents = [];

            // CASE: when importing, deleting or migrating content, lot's of model queries are happening in one transaction
            //       lot's of model events will be triggered. we ensure we set the max listeners to infinity.
            //       we are using `once` - we auto remove the listener afterwards
            if (options.importing || options.destroyAll || options.migrating) {
                options.transacting.setMaxListeners(0);
            }

            options.transacting.once('committed', (committed) => {
                if (!committed) {
                    return;
                }

                _.each(this.ghostEvents, (ghostEvent) => {
                    model._previousAttributes = previousAttributes;
                    common.events.emit(ghostEvent, model, _.omit(options, 'transacting'));
                });

                delete model.ghostEvents;
            });
        }

        model.ghostEvents.push(event);
    },

    // Bookshelf `initialize` - declare a constructor-like method for model creation
    initialize: function initialize() {
        var self = this;

        // NOTE: triggered before `creating`/`updating`
        this.on('saving', function onSaving(newObj, attrs, options) {
            if (options.method === 'insert') {
                // id = 0 is still a valid value for external usage
                if (_.isUndefined(newObj.id) || _.isNull(newObj.id)) {
                    newObj.setId();
                }
            }
        });

        [
            'fetching',
            'fetching:collection',
            'fetched',
            'fetched:collection',
            'creating',
            'created',
            'updating',
            'updated',
            'destroying',
            'destroyed',
            'saving',
            'saved'
        ].forEach(function (eventName) {
            var functionName = 'on' + eventName[0].toUpperCase() + eventName.slice(1);

            if (functionName.indexOf(':') !== -1) {
                functionName = functionName.slice(0, functionName.indexOf(':'))
                    + functionName[functionName.indexOf(':') + 1].toUpperCase()
                    + functionName.slice(functionName.indexOf(':') + 2);
                functionName = functionName.replace(':', '');
            }

            if (!self[functionName]) {
                return;
            }

            self.on(eventName, self[functionName]);
        });

        // NOTE: Please keep here. If we don't initialize the parent, bookshelf-relations won't work.
        proto.initialize.call(this);
    },

    /**
     * Do not call `toJSON`. This can remove properties e.g. password.
     * @returns {*}
     */
    onValidate: function onValidate(model, columns, options) {
        this.setEmptyValuesToNull();

        return validation.validateSchema(this.tableName, this, options);
    },

    /**
     * http://knexjs.org/#Builder-forUpdate
     * https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
     *
     * Lock target collection/model for further update operations.
     * This avoids collisions and possible content override cases.
     */
    onFetching: function onFetching(model, columns, options) {
        if (options.forUpdate && options.transacting) {
            options.query.forUpdate();
        }
    },

    onFetchingCollection: function onFetchingCollection(model, columns, options) {
        if (options.forUpdate && options.transacting) {
            options.query.forUpdate();
        }
    },

    onSaving: function onSaving(newObj) {
        // Remove any properties which don't belong on the model
        this.attributes = this.pick(this.permittedAttributes());
        // Store the previous attributes so we can tell what was updated later
        this._updatedAttributes = newObj.previousAttributes();
    },

    /**
     * Adding resources implies setting these properties on the server side
     * - set `created_by` based on the context
     * - set `updated_by` based on the context
     * - the bookshelf `timestamps` plugin sets `created_at` and `updated_at`
     *   - if plugin is disabled (e.g. import) we have a fallback condition
     *
     * Exceptions: internal context or importing
     */
    onCreating: function onCreating(newObj, attr, options) {
        if (schema.tables[this.tableName].hasOwnProperty('created_by')) {
            if (!options.importing || (options.importing && !this.get('created_by'))) {
                this.set('created_by', this.contextUser(options));
            }
        }

        if (schema.tables[this.tableName].hasOwnProperty('updated_by')) {
            if (!options.importing) {
                this.set('updated_by', this.contextUser(options));
            }
        }

        if (schema.tables[this.tableName].hasOwnProperty('created_at')) {
            if (!newObj.get('created_at')) {
                newObj.set('created_at', new Date());
            }
        }

        if (schema.tables[this.tableName].hasOwnProperty('updated_at')) {
            if (!newObj.get('updated_at')) {
                newObj.set('updated_at', new Date());
            }
        }

        return Promise.resolve(this.onValidate(newObj, attr, options));
    },

    /**
     * Changing resources implies setting these properties on the server side
     * - set `updated_by` based on the context
     * - ensure `created_at` never changes
     * - ensure `created_by` never changes
     * - the bookshelf `timestamps` plugin sets `updated_at` automatically
     *
     * Exceptions:
     *   - importing data
     *   - internal context
     *   - if no context
     */
    onUpdating: function onUpdating(newObj, attr, options) {
        if (schema.tables[this.tableName].hasOwnProperty('updated_by')) {
            if (!options.importing && !options.migrating) {
                this.set('updated_by', this.contextUser(options));
            }
        }

        if (options && options.context && !options.internal && !options.importing) {
            if (schema.tables[this.tableName].hasOwnProperty('created_at')) {
                if (newObj.hasDateChanged('created_at', {beforeWrite: true})) {
                    newObj.set('created_at', this.previous('created_at'));
                }
            }

            if (schema.tables[this.tableName].hasOwnProperty('created_by')) {
                if (newObj.hasChanged('created_by')) {
                    newObj.set('created_by', this.previous('created_by'));
                }
            }
        }

        // CASE: do not allow setting only the `updated_at` field, exception: importing
        if (schema.tables[this.tableName].hasOwnProperty('updated_at') && !options.importing) {
            if (options.migrating) {
                newObj.set('updated_at', newObj.previous('updated_at'));
            } else if (newObj.hasChanged() && Object.keys(newObj.changed).length === 1 && newObj.changed.updated_at) {
                newObj.set('updated_at', newObj.previous('updated_at'));
            }
        }

        return Promise.resolve(this.onValidate(newObj, attr, options));
    },

    /**
     * before we insert dates into the database, we have to normalize
     * date format is now in each db the same
     */
    fixDatesWhenSave: function fixDates(attrs) {
        var self = this;

        _.each(attrs, function each(value, key) {
            if (value !== null
                && schema.tables[self.tableName].hasOwnProperty(key)
                && schema.tables[self.tableName][key].type === 'dateTime') {
                attrs[key] = moment(value).format('YYYY-MM-DD HH:mm:ss');
            }
        });

        return attrs;
    },

    /**
     * all supported databases (sqlite, mysql) return different values
     *
     * sqlite:
     *   - knex returns a UTC String (2018-04-12 20:50:35)
     * mysql:
     *   - knex wraps the UTC value into a local JS Date
     */
    fixDatesWhenFetch: function fixDates(attrs) {
        var self = this, dateMoment;

        _.each(attrs, function each(value, key) {
            if (value !== null
                && schema.tables[self.tableName].hasOwnProperty(key)
                && schema.tables[self.tableName][key].type === 'dateTime') {
                dateMoment = moment(value);

                // CASE: You are somehow able to store e.g. 0000-00-00 00:00:00
                // Protect the code base and return the current date time.
                if (dateMoment.isValid()) {
                    attrs[key] = dateMoment.startOf('seconds').toDate();
                } else {
                    attrs[key] = moment().startOf('seconds').toDate();
                }
            }
        });

        return attrs;
    },

    // Convert integers to real booleans
    fixBools: function fixBools(attrs) {
        var self = this;
        _.each(attrs, function each(value, key) {
            if (schema.tables[self.tableName].hasOwnProperty(key)
                && schema.tables[self.tableName][key].type === 'bool') {
                attrs[key] = value ? true : false;
            }
        });

        return attrs;
    },

    // Sets given values to `null`
    setEmptyValuesToNull: function setEmptyValuesToNull() {
        var self = this,
            attr;

        if (!this.emptyStringProperties) {
            return;
        }

        attr = this.emptyStringProperties();

        _.each(attr, function (value) {
            if (self.get(value) === '') {
                self.set(value, null);
            }
        });
    },

    // Get the user from the options object
    contextUser: function contextUser(options) {
        options = options || {};
        options.context = options.context || {};

        if (options.context.user || ghostBookshelf.Model.isExternalUser(options.context.user)) {
            return options.context.user;
        } else if (options.context.internal) {
            return ghostBookshelf.Model.internalUser;
        } else if (this.get('id')) {
            return this.get('id');
        } else if (options.context.external) {
            return ghostBookshelf.Model.externalUser;
        } else {
            throw new common.errors.NotFoundError({
                message: common.i18n.t('errors.models.base.index.missingContext'),
                level: 'critical'
            });
        }
    },

    // format date before writing to DB, bools work
    format: function format(attrs) {
        return this.fixDatesWhenSave(attrs);
    },

    // format data and bool when fetching from DB
    parse: function parse(attrs) {
        return this.fixBools(this.fixDatesWhenFetch(attrs));
    },

    /**
     * `shallow`    - won't return relations
     * `omitPivot`  - won't return pivot fields
     *
     * `toJSON` calls `serialize`.
     *
     * @param unfilteredOptions
     * @returns {*}
     */
    toJSON: function toJSON(unfilteredOptions) {
        const options = ghostBookshelf.Model.filterOptions(unfilteredOptions, 'toJSON');
        options.omitPivot = true;

        return proto.toJSON.call(this, options);
    },

    // Get attributes that have been updated (values before a .save() call)
    updatedAttributes: function updatedAttributes() {
        return this._updatedAttributes || {};
    },

    // Get a specific updated attribute value
    updated: function updated(attr) {
        return this.updatedAttributes()[attr];
    },

    /**
     * There is difference between `updated` and `previous`:
     * Depending on the hook (before or after writing into the db), both fields have a different meaning.
     * e.g. onSaving  -> before db write (has to use previous)
     *      onUpdated -> after db write  (has to use updated)
     *
     * hasDateChanged('attr', {beforeWrite: true})
     */
    hasDateChanged: function (attr, options) {
        options = options || {};
        return moment(this.get(attr)).diff(moment(options.beforeWrite ? this.previous(attr) : this.updated(attr))) !== 0;
    },

    /**
     * we auto generate a GUID for each resource
     * no auto increment
     */
    setId: function setId() {
        this.set('id', ObjectId.generate());
    }
}, {
    // ## Data Utility Functions

    /**
     * please use these static definitions when comparing id's
     * we keep type Number, because we have too many check's where we rely on Number
     * context.user ? true : false (if context.user is 0 as number, this condition is false)
     */
    internalUser: 1,
    externalUser: 0,

    isInternalUser: function isInternalUser(id) {
        return id === ghostBookshelf.Model.internalUser || id === ghostBookshelf.Model.internalUser.toString();
    },

    isExternalUser: function isExternalUser(id) {
        return id === ghostBookshelf.Model.externalUser || id === ghostBookshelf.Model.externalUser.toString();
    },

    /**
     * Returns an array of keys permitted in every method's `options` hash.
     * Can be overridden and added to by a model's `permittedOptions` method.
     *
     * importing: is used when import a JSON file or when migrating the database
     *
     * @return {Object} Keys allowed in the `options` hash of every model's method.
     */
    permittedOptions: function permittedOptions(methodName) {
        const baseOptions = ['context', 'withRelated'];
        const extraOptions = ['transacting', 'importing', 'forUpdate', 'migrating'];

        switch (methodName) {
        case 'toJSON':
            return baseOptions.concat('shallow', 'columns');
        case 'destroy':
            return baseOptions.concat(extraOptions, ['id', 'destroyBy', 'require']);
        case 'edit':
            return baseOptions.concat(extraOptions, ['id', 'require']);
        case 'findOne':
            return baseOptions.concat(extraOptions, ['require']);
        case 'findAll':
            return baseOptions.concat(extraOptions, ['columns']);
        default:
            return baseOptions.concat(extraOptions);
        }
    },

    /**
     * Filters potentially unsafe model attributes, so you can pass them to Bookshelf / Knex.
     * This filter should be called before each insert/update operation.
     *
     * @param {Object} data Has keys representing the model's attributes/fields in the database.
     * @return {Object} The filtered results of the passed in data, containing only what's allowed in the schema.
     */
    filterData: function filterData(data) {
        var permittedAttributes = this.prototype.permittedAttributes(),
            filteredData = _.pick(data, permittedAttributes),
            sanitizedData = this.sanitizeData(filteredData);

        return sanitizedData;
    },

    /**
     * `sanitizeData` ensures that client data is in the correct format for further operations.
     *
     * Dates:
     * - client dates are sent as ISO 8601 format (moment(..).format())
     * - server dates are in JS Date format
     *   >> when bookshelf fetches data from the database, all dates are in JS Dates
     *   >> see `parse`
     * - Bookshelf updates the model with the new client data via the `set` function
     * - Bookshelf uses a simple `isEqual` function from lodash to detect real changes
     * - .previous(attr) and .get(attr) returns false obviously
     * - internally we use our `hasDateChanged` if we have to compare previous/updated dates
     * - but Bookshelf is not in our control for this case
     *
     * @IMPORTANT
     * Before the new client data get's inserted again, the dates get's re-transformed into
     * proper strings, see `format`.
     *
     * @IMPORTANT
     * Sanitize relations.
     */
    sanitizeData: function sanitizeData(data) {
        var tableName = _.result(this.prototype, 'tableName'), date;

        _.each(data, (value, property) => {
            if (value !== null
                && schema.tables[tableName].hasOwnProperty(property)
                && schema.tables[tableName][property].type === 'dateTime'
                && typeof value === 'string'
            ) {
                date = new Date(value);

                // CASE: client sends `0000-00-00 00:00:00`
                if (isNaN(date)) {
                    throw new common.errors.ValidationError({
                        message: common.i18n.t('errors.models.base.invalidDate', {key: property}),
                        code: 'DATE_INVALID'
                    });
                }

                data[property] = moment(value).toDate();
            }

            if (this.prototype.relationships && this.prototype.relationships.indexOf(property) !== -1) {
                _.each(data[property], (relation, indexInArr) => {
                    _.each(relation, (value, relationProperty) => {
                        if (value !== null
                            && schema.tables[this.prototype.relationshipBelongsTo[property]].hasOwnProperty(relationProperty)
                            && schema.tables[this.prototype.relationshipBelongsTo[property]][relationProperty].type === 'dateTime'
                            && typeof value === 'string'
                        ) {
                            date = new Date(value);

                            // CASE: client sends `0000-00-00 00:00:00`
                            if (isNaN(date)) {
                                throw new common.errors.ValidationError({
                                    message: common.i18n.t('errors.models.base.invalidDate', {key: relationProperty}),
                                    code: 'DATE_INVALID'
                                });
                            }

                            data[property][indexInArr][relationProperty] = moment(value).toDate();
                        }
                    });
                });
            }
        });

        return data;
    },

    /**
     * Filters potentially unsafe `options` in a model method's arguments, so you can pass them to Bookshelf / Knex.
     * @param {Object} unfilteredOptions Represents options to filter in order to be passed to the Bookshelf query.
     * @param {String} methodName The name of the method to check valid options for.
     * @return {Object} The filtered results of `options`.
     */
    filterOptions: function filterOptions(unfilteredOptions, methodName, filterConfig) {
        unfilteredOptions = unfilteredOptions || {};
        filterConfig = filterConfig || {};

        if (unfilteredOptions.hasOwnProperty('include')) {
            throw new common.errors.IncorrectUsageError({
                message: 'The model layer expects using `withRelated`.'
            });
        }

        var options = _.cloneDeep(unfilteredOptions),
            extraAllowedProperties = filterConfig.extraAllowedProperties || [],
            permittedOptions;

        permittedOptions = this.permittedOptions(methodName, options);
        permittedOptions = _.union(permittedOptions, extraAllowedProperties);
        options = _.pick(options, permittedOptions);

        if (this.defaultRelations) {
            options = this.defaultRelations(methodName, options);
        }

        return options;
    },

    // ## Model Data Functions

    /**
     * ### Find All
     * Fetches all the data for a particular model
     * @param {Object} unfilteredOptions (optional)
     * @return {Promise(ghostBookshelf.Collection)} Collection of all Models
     */
    findAll: function findAll(unfilteredOptions) {
        var options = this.filterOptions(unfilteredOptions, 'findAll'),
            itemCollection = this.forge();

        // transforms fictive keywords like 'all' (status:all) into correct allowed values
        if (this.processOptions) {
            this.processOptions(options);
        }

        // @TODO: we can't use order raw when running migrations (see https://github.com/tgriesser/knex/issues/2763)
        if (this.orderDefaultRaw && !options.migrating) {
            itemCollection.query((qb) => {
                qb.orderByRaw(this.orderDefaultRaw());
            });
        }

        itemCollection.applyDefaultAndCustomFilters(options);
        return itemCollection.fetchAll(options).then(function then(result) {
            if (options.withRelated) {
                _.each(result.models, function each(item) {
                    item.withRelated = options.withRelated;
                });
            }

            return result;
        });
    },

    /**
     * ### Find Page
     * Find results by page - returns an object containing the
     * information about the request (page, limit), along with the
     * info needed for pagination (pages, total).
     *
     * **response:**
     *
     *     {
     *         data: [
     *             {...}, ...
     *         ],
     *         meta: {
     *             pagination: {
     *                 page: __,
     *                 limit: __,
     *                 pages: __,
     *                 total: __
     *             }
     *         }
     *     }
     *
     * @param {Object} unfilteredOptions
     */
    findPage: function findPage(unfilteredOptions) {
        var options = this.filterOptions(unfilteredOptions, 'findPage'),
            itemCollection = this.forge(),
            requestedColumns = options.columns;

        // Set this to true or pass ?debug=true as an API option to get output
        itemCollection.debug = options.debug && config.get('env') !== 'production';

        // This applies default properties like 'staticPages' and 'status'
        // And then converts them to 'where' options... this behaviour is effectively deprecated in favour
        // of using filter - it's only be being kept here so that we can transition cleanly.
        if (this.processOptions) {
            this.processOptions(options);
        }

        // Add Filter behaviour
        itemCollection.applyDefaultAndCustomFilters(options);

        // Ensure only valid fields/columns are added to query
        // and append default columns to fetch
        if (options.columns) {
            options.columns = _.intersection(options.columns, this.prototype.permittedAttributes());
            options.columns = _.union(options.columns, this.prototype.defaultColumnsToFetch());
        }

        if (options.order) {
            options.order = this.parseOrderOption(options.order, options.withRelated);
        } else if (this.orderDefaultRaw) {
            options.orderRaw = this.orderDefaultRaw();
        } else if (this.orderDefaultOptions) {
            options.order = this.orderDefaultOptions();
        }

        return itemCollection.fetchPage(options).then(function formatResponse(response) {
            // Attributes are being filtered here, so they are not leaked into calling layer
            // where models are serialized to json and do not do more filtering.
            // Re-add and pick any computed properties that were stripped before fetchPage call.
            const data = response.collection.models.map((model) => {
                if (requestedColumns) {
                    model.attributes = _.pick(model.attributes, requestedColumns);
                    model._previousAttributes = _.pick(model._previousAttributes, requestedColumns);
                }

                return model;
            });

            return {
                data: data,
                meta: {pagination: response.pagination}
            };
        });
    },

    /**
     * ### Find One
     * Naive find one where data determines what to match on
     * @param {Object} data
     * @param {Object} unfilteredOptions (optional)
     * @return {Promise(ghostBookshelf.Model)} Single Model
     */
    findOne: function findOne(data, unfilteredOptions) {
        var options = this.filterOptions(unfilteredOptions, 'findOne');
        data = this.filterData(data);
        return this.forge(data).fetch(options);
    },

    /**
     * ### Edit
     * Naive edit
     *
     * We always forward the `method` option to Bookshelf, see http://bookshelfjs.org/#Model-instance-save.
     * Based on the `method` option Bookshelf and Ghost can determine if a query is an insert or an update.
     *
     * @param {Object} data
     * @param {Object} unfilteredOptions (optional)
     * @return {Promise(ghostBookshelf.Model)} Edited Model
     */
    edit: function edit(data, unfilteredOptions) {
        const options = this.filterOptions(unfilteredOptions, 'edit');
        const id = options.id;
        const model = this.forge({id: id});

        data = this.filterData(data);

        // We allow you to disable timestamps when run migration, so that the posts `updated_at` value is the same
        if (options.importing) {
            model.hasTimestamps = false;
        }

        return model.fetch(options).then(function then(object) {
            if (object) {
                options.method = 'update';
                return object.save(data, options);
            }
        });
    },

    /**
     * ### Add
     * Naive add
     * @param {Object} data
     * @param {Object} unfilteredOptions (optional)
     * @return {Promise(ghostBookshelf.Model)} Newly Added Model
     */
    add: function add(data, unfilteredOptions) {
        var options = this.filterOptions(unfilteredOptions, 'add'),
            model;

        data = this.filterData(data);
        model = this.forge(data);

        // We allow you to disable timestamps when importing posts so that the new posts `updated_at` value is the same
        // as the import json blob. More details refer to https://github.com/TryGhost/Ghost/issues/1696
        if (options.importing) {
            model.hasTimestamps = false;
        }

        // Bookshelf determines whether an operation is an update or an insert based on the id
        // Ghost auto-generates Object id's, so we need to tell Bookshelf here that we are inserting data
        options.method = 'insert';
        return model.save(null, options);
    },

    /**
     * ### Destroy
     * Naive destroy
     * @param {Object} unfilteredOptions (optional)
     * @return {Promise(ghostBookshelf.Model)} Empty Model
     */
    destroy: function destroy(unfilteredOptions) {
        const options = this.filterOptions(unfilteredOptions, 'destroy');
        if (!options.destroyBy) {
            options.destroyBy = {
                id: options.id
            };
        }

        // Fetch the object before destroying it, so that the changed data is available to events
        return this.forge(options.destroyBy)
            .fetch(options)
            .then(function then(obj) {
                return obj.destroy(options);
            });
    },

    /**
     * ### Generate Slug
     * Create a string to act as the permalink for an object.
     * @param {ghostBookshelf.Model} Model Model type to generate a slug for
     * @param {String} base The string for which to generate a slug, usually a title or name
     * @param {Object} options Options to pass to findOne
     * @return {Promise(String)} Resolves to a unique slug string
     */
    generateSlug: function generateSlug(Model, base, options) {
        var slug,
            slugTryCount = 1,
            baseName = Model.prototype.tableName.replace(/s$/, ''),
            // Look for a matching slug, append an incrementing number if so
            checkIfSlugExists, longSlug;

        checkIfSlugExists = function checkIfSlugExists(slugToFind) {
            var args = {slug: slugToFind};

            // status is needed for posts
            if (options && options.status) {
                args.status = options.status;
            }

            return Model.findOne(args, options).then(function then(found) {
                var trimSpace;

                if (!found) {
                    return slugToFind;
                }

                slugTryCount += 1;

                // If we shortened, go back to the full version and try again
                if (slugTryCount === 2 && longSlug) {
                    slugToFind = longSlug;
                    longSlug = null;
                    slugTryCount = 1;
                    return checkIfSlugExists(slugToFind);
                }

                // If this is the first time through, add the hyphen
                if (slugTryCount === 2) {
                    slugToFind += '-';
                } else {
                    // Otherwise, trim the number off the end
                    trimSpace = -(String(slugTryCount - 1).length);
                    slugToFind = slugToFind.slice(0, trimSpace);
                }

                slugToFind += slugTryCount;

                return checkIfSlugExists(slugToFind);
            });
        };

        // the slug may never be longer than the allowed limit of 191 chars, but should also
        // take the counter into count. We reduce a too long slug to 185 so we're always on the
        // safe side, also in terms of checking for existing slugs already.
        slug = security.string.safe(base, options);

        if (slug.length > 185) {
            // CASE: don't cut the slug on import
            if (!_.has(options, 'importing') || !options.importing) {
                slug = slug.slice(0, 185);
            }
        }

        // If it's a user, let's try to cut it down (unless this is a human request)
        if (baseName === 'user' && options && options.shortSlug && slugTryCount === 1 && slug !== 'ghost-owner') {
            longSlug = slug;
            slug = (slug.indexOf('-') > -1) ? slug.substr(0, slug.indexOf('-')) : slug;
        }

        if (!_.has(options, 'importing') || !options.importing) {
            // This checks if the first character of a tag name is a #. If it is, this
            // is an internal tag, and as such we should add 'hash' to the beginning of the slug
            if (baseName === 'tag' && /^#/.test(base)) {
                slug = 'hash-' + slug;
            }
        }

        // Check the filtered slug doesn't match any of the reserved keywords
        return filters.doFilter('slug.reservedSlugs', config.get('slugs').reserved).then(function then(slugList) {
            // Some keywords cannot be changed
            slugList = _.union(slugList, urlService.utils.getProtectedSlugs());

            return _.includes(slugList, slug) ? slug + '-' + baseName : slug;
        }).then(function then(slug) {
            // if slug is empty after trimming use the model name
            if (!slug) {
                slug = baseName;
            }
            // Test for duplicate slugs.
            return checkIfSlugExists(slug);
        });
    },

    parseOrderOption: function (order, withRelated) {
        var permittedAttributes, result, rules;

        permittedAttributes = this.prototype.permittedAttributes();
        if (withRelated && withRelated.indexOf('count.posts') > -1) {
            permittedAttributes.push('count.posts');
        }
        result = {};
        rules = order.split(',');

        _.each(rules, function (rule) {
            var match, field, direction;

            match = /^([a-z0-9_.]+)\s+(asc|desc)$/i.exec(rule.trim());

            // invalid order syntax
            if (!match) {
                return;
            }

            field = match[1].toLowerCase();
            direction = match[2].toUpperCase();

            if (permittedAttributes.indexOf(field) === -1) {
                return;
            }

            result[field] = direction;
        });

        return result;
    },

    /**
     * All models which have a visibility property, can use this static helper function.
     * Filter models by visibility.
     *
     * @param {Array|Object} items
     * @param {Array} visibility
     * @param {Boolean} [explicit]
     * @param {Function} [fn]
     * @returns {Array|Object} filtered items
     */
    filterByVisibility: function filterByVisibility(items, visibility, explicit, fn) {
        var memo = _.isArray(items) ? [] : {};

        if (_.includes(visibility, 'all')) {
            return fn ? _.map(items, fn) : items;
        }

        // We don't want to change the structure of what is returned
        return _.reduce(items, function (items, item, key) {
            if (!item.visibility && !explicit || _.includes(visibility, item.visibility)) {
                var newItem = fn ? fn(item) : item;
                if (_.isArray(items)) {
                    memo.push(newItem);
                } else {
                    memo[key] = newItem;
                }
            }
            return memo;
        }, memo);
    },

    /**
     * Returns an Array of visibility values.
     * e.g. public,all => ['public, 'all']
     * @param visibility
     * @returns {*}
     */
    parseVisibilityString: function parseVisibilityString(visibility) {
        if (!visibility) {
            return ['public'];
        }

        return _.map(visibility.split(','), _.trim);
    },

    /**
     * If you want to fetch all data fast, i recommend using this function.
     * Bookshelf is just too slow, too much ORM overhead.
     *
     * If we e.g. instantiate for each object a model, it takes twice long.
     */
    raw_knex: {
        fetchAll: function (options) {
            options = options || {};

            const modelName = options.modelName;
            const tableNames = {
                Post: 'posts',
                User: 'users',
                Tag: 'tags'
            };
            const exclude = options.exclude;
            const filter = options.filter;
            const withRelated = options.withRelated;
            const withRelatedFields = options.withRelatedFields;
            const relations = {
                tags: {
                    targetTable: 'tags',
                    name: 'tags',
                    innerJoin: {
                        relation: 'posts_tags',
                        condition: ['posts_tags.tag_id', '=', 'tags.id']
                    },
                    select: ['posts_tags.post_id as post_id', 'tags.visibility'],
                    whereIn: 'posts_tags.post_id',
                    whereInKey: 'post_id',
                    orderBy: 'sort_order'
                },
                authors: {
                    targetTable: 'users',
                    name: 'authors',
                    innerJoin: {
                        relation: 'posts_authors',
                        condition: ['posts_authors.author_id', '=', 'users.id']
                    },
                    select: ['posts_authors.post_id as post_id'],
                    whereIn: 'posts_authors.post_id',
                    whereInKey: 'post_id',
                    orderBy: 'sort_order'
                }
            };

            let query = ghostBookshelf.knex(tableNames[modelName]);

            if (options.offset) {
                query.offset(options.offset);
            }

            if (options.limit) {
                query.limit(options.limit);
            }

            // exclude fields if enabled
            if (exclude) {
                const toSelect = _.keys(schema.tables[tableNames[modelName]]);

                _.each(exclude, (key) => {
                    if (toSelect.indexOf(key) !== -1) {
                        toSelect.splice(toSelect.indexOf(key), 1);
                    }
                });

                query.select(toSelect);
            }

            // filter data
            gql.knexify(query, gql.parse(filter));

            return query.then((objects) => {
                debug('fetched', modelName, filter);

                if (!objects.length) {
                    debug('No more entries found');
                    return Promise.resolve([]);
                }

                let props = {};

                if (!withRelated) {
                    return _.map(objects, (object) => {
                        object = ghostBookshelf._models[modelName].prototype.toJSON.bind({
                            attributes: object,
                            related: function (key) {
                                return object[key];
                            },
                            serialize: ghostBookshelf._models[modelName].prototype.serialize,
                            formatsToJSON: ghostBookshelf._models[modelName].prototype.formatsToJSON
                        })();

                        object = ghostBookshelf._models[modelName].prototype.fixBools(object);
                        object = ghostBookshelf._models[modelName].prototype.fixDatesWhenFetch(object);
                        return object;
                    });
                }

                _.each(withRelated, (withRelatedKey) => {
                    const relation = relations[withRelatedKey];

                    props[relation.name] = (() => {
                        debug('fetch withRelated', relation.name);

                        let query = db.knex(relation.targetTable);

                        // default fields to select
                        _.each(relation.select, (fieldToSelect) => {
                            query.select(fieldToSelect);
                        });

                        // custom fields to select
                        _.each(withRelatedFields[withRelatedKey], (toSelect) => {
                            query.select(toSelect);
                        });

                        query.innerJoin(
                            relation.innerJoin.relation,
                            relation.innerJoin.condition[0],
                            relation.innerJoin.condition[1],
                            relation.innerJoin.condition[2]
                        );

                        query.whereIn(relation.whereIn, _.map(objects, 'id'));
                        query.orderBy(relation.orderBy);

                        return query
                            .then((relations) => {
                                debug('fetched withRelated', relation.name);

                                // arr => obj[post_id] = [...] (faster access)
                                return relations.reduce((obj, item) => {
                                    if (!obj[item[relation.whereInKey]]) {
                                        obj[item[relation.whereInKey]] = [];
                                    }

                                    obj[item[relation.whereInKey]].push(_.omit(item, relation.select));
                                    return obj;
                                }, {});
                            });
                    })();
                });

                return Promise.props(props)
                    .then((relations) => {
                        debug('attach relations', modelName);

                        objects = _.map(objects, (object) => {
                            _.each(Object.keys(relations), (relation) => {
                                if (!relations[relation][object.id]) {
                                    object[relation] = [];
                                    return;
                                }

                                object[relation] = relations[relation][object.id];
                            });

                            object = ghostBookshelf._models[modelName].prototype.toJSON.bind({
                                attributes: object,
                                _originalOptions: {
                                    withRelated: Object.keys(relations)
                                },
                                related: function (key) {
                                    return object[key];
                                },
                                serialize: ghostBookshelf._models[modelName].prototype.serialize,
                                formatsToJSON: ghostBookshelf._models[modelName].prototype.formatsToJSON
                            })();

                            object = ghostBookshelf._models[modelName].prototype.fixBools(object);
                            object = ghostBookshelf._models[modelName].prototype.fixDatesWhenFetch(object);
                            return object;
                        });

                        debug('attached relations', modelName);

                        return objects;
                    });
            });
        }
    }
});

// Export ghostBookshelf for use elsewhere
module.exports = ghostBookshelf;


text:
require 'digest/md5'

module Simplesol
  module Configuration
    VALID_OPTIONS_KEYS = [:login, :api_key, :debug, :api_server]

    DEFAULT_LOGIN      = nil
    DEFAULT_API_KEY    = nil
    DEFAULT_DEBUG      = true
    DEFAULT_API_SERVER = 'http://api.gosmsinfo.ru/api'

    attr_accessor *VALID_OPTIONS_KEYS

    def configured?
      status = login && api_key && api_server
      !(status.nil? || status.empty?)
    end

    def configure
      yield self
    end

    def options
      VALID_OPTIONS_KEYS.inject({}) do |option, key|
        option.merge!(key => send(key))
      end
    end

    def reset
      VALID_OPTIONS_KEYS.each do |key|
        send("#{key}=", Simplesol::Configuration.const_get("default_#{key}".upcase))
      end
    end
  end
end


text:
package com.inari.commons.lang.functional;

public interface CharArrayFunction {

    char f( char... values );

}


text:
BEGIN_HYPERDEX_NAMESPACE

class datatype_timestamp : public datatype_info
{
    public:
        datatype_timestamp(hyperdatatype t);
        virtual ~datatype_timestamp() throw ();

    public:
        virtual hyperdatatype datatype() const;
        virtual bool validate(const e::slice& value) const;
        virtual bool check_args(const funcall& func) const;
        virtual bool apply(const e::slice& old_value,
                           const funcall* funcs, size_t funcs_sz,
                           e::arena* new_memory,
                           e::slice* new_value) const;

    public:
        virtual bool hashable() const;
        virtual uint64_t hash(const e::slice& value) const;
        virtual bool indexable() const;

    public:
        virtual bool containable() const;
        virtual bool step(const uint8_t** ptr,
                          const uint8_t* end,
                          e::slice* elem) const;
        virtual uint64_t write_sz(const e::slice& elem) const;
        virtual uint8_t* write(const e::slice& elem,
                               uint8_t* write_to) const;

    public:
        virtual bool comparable() const;
        virtual int compare(const e::slice& lhs, const e::slice& rhs) const;
        virtual compares_less compare_less() const;

    private:
        hyperdatatype m_type;
};

END_HYPERDEX_NAMESPACE

#endif // hyperdex_common_datatype_string_h_


text:
layout: post

#event information
title:  "Mission to Wales with The Turning ─ 6th to 15th July"
cover: "img/M2W-slide-2017.png"
date:   2017-07-15
start_time: "10:00"
end_time: "15:30"

#event organiser details

organiser: "The Turning"

---

Following a successful pilot in Swansea, New Wine Cymru are launching a nationwide mission to 30 towns and cities across Wales.


text:
<?php defined('BASEPATH') OR exit('No direct script access allowed');

class Slider extends CI_Controller {

	function __construct() {
		parent::__construct();
		if(!$this->session->userdata('email')) {redirect(base_url('admin/login'));}
		$this->load->model(array('Configs_ad_model', 'Slider_ad_model'));
		$this->pageTitle = 'სლაიდერი';
	}

	public function index()
	{
		$data['pageTitle'] = $this->pageTitle;
		$data['parents'] = $this->Slider_ad_model->read_parents();
		$this->load->view('admin/slider/manage', $data);
	}

	public function create()
	{
		$data['pageTitle'] = $this->pageTitle;
		$data['langs'] = $this->Configs_ad_model->langs();

		$this->load->view('admin/slider/create', $data);

		if($this->input->post()) {
			$this->Slider_ad_model->create($data['langs']);
			redirect(base_url().'admin/slider');
		}
	}

	public function update()
	{
		$data['pageTitle'] = $this->pageTitle;
		$data['langs'] = $this->Configs_ad_model->langs();
		$data['item'] = $this->Slider_ad_model->read();

		$this->load->view('admin/slider/update', $data);

		if($this->input->post()) {
			$this->Slider_ad_model->update();
			redirect(base_url().'admin/slider');
		}
	}

	public function update_sorting()
	{
		$data['slider'] = $this->Slider_ad_model->update_sorting();
	}

	public function delete()
	{
		$this->Slider_ad_model->delete();
		redirect(base_url().'admin/slider');
	}

}


text:
CC:=i686-elf-gcc
LD:=i686-elf-gcc
AS:=i686-elf-as
CFLAGS:=-c -ffreestanding -std=gnu99 -O2 \
	-Wall -Wextra -Werror -Wno-unused-parameter \
	-Wno-unused-variable -Wno-unused-but-set-variable

.PHONY: qemu run

mercury-kernel: kernel/arch/$(ARCH)/kernel.ld kernel/arch/$(ARCH)/boot.o ${KERNEL_OBJS}
	$(LD) -o mercury-kernel -T kernel/arch/$(ARCH)/kernel.ld -nostdlib -ffreestanding -O2 kernel/arch/$(ARCH)/boot.o ${KERNEL_OBJS} -lgcc

kernel/arch/$(ARCH)/boot.o: kernel/arch/$(ARCH)/boot.S
	$(AS) kernel/arch/$(ARCH)/boot.S -o kernel/arch/$(ARCH)/boot.o

qemu: mercury-kernel
	qemu-system-x86_64 -m 1G -enable-kvm -kernel mercury-kernel

run: qemu


text:
<?xml version="1.0" encoding="UTF-8" ?>
<!--
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 -->

<configurationParameters defaultGroup="default" searchStrategy="default_fallback">
	<commonParameters>
			<configurationParameter>
				<name>FeatureIndex</name>
				<description>The name of the index to use.</description>
				<type>String</type>
				<multiValued>false</multiValued>
				<mandatory>true</mandatory>
			</configurationParameter>
	</commonParameters>
	<configurationGroup names="default" />
</configurationParameters>