text:
leaderboard-tinytests
=====================

Unit tests for the Leaderboard example using Tinytest.

#### Installation

````sh
// grab a copy of meteor
git clone http://github.com/meteor/meteor.git

// so you can fuss with the leaderboard example
cd meteor/examples/leaderboard/packages

// install the tinytests
git clone http://github.com/awatson1978/leaderboard-tinytests.git

// make sure the tiny test package is installed
cd ..
meteor add tinytests

// and run them
meteor test-packages leaderboard-tinytests
````

Check ``http://localhost:3000`` to see them run.

#### Notes

The ``fireEvent`` stub isn't mocked up to anything, which is why the last test is failing.


text:
"""
Tests For Scheduler
"""

import mock

from nova import context
from nova import db
from nova.scheduler import driver
from nova.scheduler import host_manager
from nova.scheduler import manager
from nova import servicegroup
from nova import test
from nova.tests.unit import fake_server_actions

class SchedulerManagerTestCase(test.NoDBTestCase):
    """Test case for scheduler manager."""

    manager_cls = manager.SchedulerManager
    driver_cls = driver.Scheduler
    driver_cls_name = 'nova.scheduler.driver.Scheduler'

    @mock.patch.object(host_manager.HostManager, '_init_instance_info')
    @mock.patch.object(host_manager.HostManager, '_init_aggregates')
    def setUp(self, mock_init_agg, mock_init_inst):
        super(SchedulerManagerTestCase, self).setUp()
        self.flags(scheduler_driver=self.driver_cls_name)
        with mock.patch.object(host_manager.HostManager, '_init_aggregates'):
            self.manager = self.manager_cls()
        self.context = context.RequestContext('fake_user', 'fake_project')
        self.topic = 'fake_topic'
        self.fake_args = (1, 2, 3)
        self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'}
        fake_server_actions.stub_out_action_events(self.stubs)

    def test_1_correct_init(self):
        # Correct scheduler driver
        manager = self.manager
        self.assertIsInstance(manager.driver, self.driver_cls)

    def test_select_destination(self):
        with mock.patch.object(self.manager.driver, 'select_destinations'
                ) as select_destinations:
            self.manager.select_destinations(None, None, {})
            select_destinations.assert_called_once_with(None, None, {})

    def test_update_aggregates(self):
        with mock.patch.object(self.manager.driver.host_manager,
                               'update_aggregates'
                ) as update_aggregates:
            self.manager.update_aggregates(None, aggregates='agg')
            update_aggregates.assert_called_once_with('agg')

    def test_delete_aggregate(self):
        with mock.patch.object(self.manager.driver.host_manager,
                               'delete_aggregate'
                ) as delete_aggregate:
            self.manager.delete_aggregate(None, aggregate='agg')
            delete_aggregate.assert_called_once_with('agg')

    def test_update_instance_info(self):
        with mock.patch.object(self.manager.driver.host_manager,
                               'update_instance_info') as mock_update:
            self.manager.update_instance_info(mock.sentinel.context,
                                              mock.sentinel.host_name,
                                              mock.sentinel.instance_info)
            mock_update.assert_called_once_with(mock.sentinel.context,
                                                mock.sentinel.host_name,
                                                mock.sentinel.instance_info)

    def test_delete_instance_info(self):
        with mock.patch.object(self.manager.driver.host_manager,
                               'delete_instance_info') as mock_delete:
            self.manager.delete_instance_info(mock.sentinel.context,
                                              mock.sentinel.host_name,
                                              mock.sentinel.instance_uuid)
            mock_delete.assert_called_once_with(mock.sentinel.context,
                                                mock.sentinel.host_name,
                                                mock.sentinel.instance_uuid)

    def test_sync_instance_info(self):
        with mock.patch.object(self.manager.driver.host_manager,
                               'sync_instance_info') as mock_sync:
            self.manager.sync_instance_info(mock.sentinel.context,
                                            mock.sentinel.host_name,
                                            mock.sentinel.instance_uuids)
            mock_sync.assert_called_once_with(mock.sentinel.context,
                                              mock.sentinel.host_name,
                                              mock.sentinel.instance_uuids)

class SchedulerTestCase(test.NoDBTestCase):
    """Test case for base scheduler driver class."""

    # So we can subclass this test and re-use tests if we need.
    driver_cls = driver.Scheduler

    @mock.patch.object(host_manager.HostManager, '_init_instance_info')
    @mock.patch.object(host_manager.HostManager, '_init_aggregates')
    def setUp(self, mock_init_agg, mock_init_inst):
        super(SchedulerTestCase, self).setUp()
        self.driver = self.driver_cls()
        self.context = context.RequestContext('fake_user', 'fake_project')
        self.topic = 'fake_topic'
        self.servicegroup_api = servicegroup.API()

    def test_hosts_up(self):
        service1 = {'host': 'host1'}
        service2 = {'host': 'host2'}
        services = [service1, service2]

        self.mox.StubOutWithMock(db, 'service_get_all_by_topic')
        self.mox.StubOutWithMock(servicegroup.API, 'service_is_up')

        db.service_get_all_by_topic(self.context,
                self.topic).AndReturn(services)
        self.servicegroup_api.service_is_up(service1).AndReturn(False)
        self.servicegroup_api.service_is_up(service2).AndReturn(True)

        self.mox.ReplayAll()
        result = self.driver.hosts_up(self.context, self.topic)
        self.assertEqual(result, ['host2'])

class SchedulerDriverBaseTestCase(SchedulerTestCase):
    """Test cases for base scheduler driver class methods
       that will fail if the driver is changed.
    """

    def test_unimplemented_select_destinations(self):
        self.assertRaises(NotImplementedError,
                self.driver.select_destinations, self.context, {}, {})

class SchedulerInstanceGroupData(test.NoDBTestCase):

    driver_cls = driver.Scheduler

    def setUp(self):
        super(SchedulerInstanceGroupData, self).setUp()
        self.user_id = 'fake_user'
        self.project_id = 'fake_project'
        self.context = context.RequestContext(self.user_id, self.project_id)
        self.driver = self.driver_cls()

    def _get_default_values(self):
        return {'name': 'fake_name',
                'user_id': self.user_id,
                'project_id': self.project_id}

    def _create_instance_group(self, context, values, policies=None,
                               metadata=None, members=None):
        return db.instance_group_create(context, values, policies=policies,
                                        metadata=metadata, members=members)

redpajama_set_name: RedPajamaGithub


text:
'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	Schema = mongoose.Schema;

/**
 * Tasklist Schema
 */
var TasklistSchema = new Schema({
  title: {
    type: String,
    required: 'Each tasklist needs a title'
  },
  description: {
    type: String,
    required: 'Each task needs a short description'
  },
  tasks: [{
    type: Schema.ObjectId,
    ref: 'Task'
  }],
  date: {
    type: String,
    default: Date.now()
  },
  createdBy: {
    type: Schema.ObjectId,
    ref: 'User',
    required: true
  }
});

mongoose.model('Tasklist', TasklistSchema);


text:
var should = require('chai').should(),
  Validator = require('../index');

describe('filters', function () {
  it('filters out those data not specified in the schema', function () {
    var schema = {
      'id': '/Item',
      'type': 'object',
      'properties': {
        'name': {
          'title': 'Item\'s name',
          'type': 'string',
          'required': true
        },
        'description': {
          'title': 'Item\'s description',
          'type': 'string'
        }
      }
    };

    var instance = {
      name: 'A name',
      description: 'Some description',
      junkData: 'Junk data must be filtered out from the instance!!!'
    };

    var v = new Validator();
    var report = v.validate(instance, schema);

    should.not.exist(report.instance.junkData);
  });
});


text:
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function(defaults) {
  var app = new EmberApp(defaults, {
    snippetPaths: ['tests/dummy/snippets'],
    snippetSearchPaths: ['app', 'tests/dummy/app', 'addon'],
    minifyJS: { enabled: false }
  });

  var bootstrapPath = app.bowerDirectory + '/bootstrap/dist/';
  app.import(bootstrapPath + 'css/bootstrap.css');
  app.import(bootstrapPath + 'fonts/glyphicons-halflings-regular.eot', { destDir: '/fonts' });
  app.import(bootstrapPath + 'fonts/glyphicons-halflings-regular.svg', { destDir: '/fonts' });
  app.import(bootstrapPath + 'fonts/glyphicons-halflings-regular.ttf', { destDir: '/fonts' });
  app.import(bootstrapPath + 'fonts/glyphicons-halflings-regular.woff', { destDir: '/fonts' });
  app.import(bootstrapPath + 'fonts/glyphicons-halflings-regular.woff2', { destDir: '/fonts' });

  /*
    This build file specifes the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree();
};


text:
/**
 * Remove any undefined value or empty string from the object given.
 * Does NOT mutate the original object. Instead, a shallow clone will be returned.
 */
export function removeUndef(obj: any): any {
  const cloned = {...obj}
  Object.entries(cloned).forEach(([key, value]) => (value == null || value === '') && delete cloned[key])
  return cloned
}


text:
package com.adeptj.modules.restclient.okhttp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.lang.invoke.MethodHandles;
import java.util.UUID;

class OkHttpRestClientLogger {

    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private static final String REQUEST_START = "<<==================== Request Processing Start ====================>>\n";

    private static final String REQUEST_END = "<<==================== Request Processing Complete ====================>>";

    private static final String RESPONSE_START = "<<==================== Response ====================>>\n";

    private static final String REQ_FMT
            = "\n{}\n Request ID: {}\n Request Method: {}\n Request URI: {}\n Request Headers:\n{}\n Request Body:\n{}";

    private static final String RESP_FMT
            = "\n{}\n Request ID: {}\n Response Status: {}\n Response Headers:\n{}\n Response Body:\n{}\n Total Time: {} milliseconds\n\n{}";

    private static String getReqId(String mdcReqIdAttrName) {
        String reqId = MDC.get(mdcReqIdAttrName);
        // Just in case MDC attribute is not set up by application code earlier.
        if (reqId == null) {
            reqId = UUID.randomUUID().toString();
        }
        return reqId;
    }
}


text:
import re

from django import template
from django.template.defaultfilters import stringfilter, force_escape
from django.utils.safestring import mark_safe
from django.contrib.contenttypes.models import ContentType

from annotatetext.models import Annotation
from annotatetext.forms import NewAnnotationForm

register = template.Library()

@register.filter
@stringfilter
def simple_formatting(value):
    value = force_escape(value)
    value = value.replace("\r\n", "\n")
    value = "
%s
" % value
    value = re.sub("(?sum) - (.*?)\n", "
 - \\1
\n", value)
    value = re.sub("(?sum) ([a-z0-9]+\)) (.*?)\n", "
 \\1 \\2
\n", value)
    value = re.sub("(?sum)((?:
.*?
\n)+)", "

\\1

", value)
    value = re.sub("(?sum)\n\n", "\n\n

", value)
    value = re.sub("(?sum)

(\s+)

", "
\\1
", value)
    value = re.sub("(?sum)

(\s+)

", "\\1

", value)
    value = re.sub("(?sum)([^(?:
|
|\n)])\n([^(?:
|
|\n
|
)])", "\\1
\n\\2", value)
    return mark_safe(value)

def find(f, seq):
  """Return first item in sequence where f(item) == True."""
  for item in seq:
    if f(item):
      return item

@register.tag(name='get_latest_annotations')
def do_get_latest_annotations(parser, token):
    """ Use like so: {% get_latest_annotations as annotations [count=1]%}
    """
    as_var = None
    count = 1
    try:
        contents = token.split_contents()
        if len(contents) >= 2 and contents[1] == "as":
            as_var = contents[2]
        if len(contents) == 4:
            count = int(contents[3])
    except (ValueError, IndexError):
        raise template.TemplateSyntaxError, "Tag Syntax Error: %r as annotations [count=1]" % token.contents.split()[0]
    return LatestAnnotationsNode(as_var, count)

class LatestAnnotationsNode(template.Node):
    def __init__(self, as_var, count):
        self.as_var = as_var
        self.count = count

    def render(self, context):
         annotations = Annotation.objects.select_related("content_type").order_by("-timestamp")[:self.count]
         context[self.as_var] = annotations
         return ''

@register.tag(name='get_annotations_for')
def do_get_annotations_for(parser, token):
    """Use like so: {% get_annotations_for object_list as var_name %}
    Note: objects in object_list must be of one ContentType!
    var_name is a dict that contains the id of every object in object_list as key.
    The value is another dict with two keys: "annotations" as a list of annotations and
    "form" as an annotation form instance
    """
    as_var = None
    try:
        contents = token.split_contents()
        instance_name = contents[1]
        if len(contents) >= 4 and contents[2] == "as":
            as_var = contents[3]

    except (ValueError, IndexError):
        raise template.TemplateSyntaxError, "Tag Syntax Error: %r object_list as var_name" % token.contents.split()[0]
    return InsertAnnotationNode(instance_name, as_var)

class InsertAnnotationNode(template.Node):
    def __init__(self, instance_name, as_var):
        self.instance_var = template.Variable(instance_name)
        self.as_var = as_var

    def render(self, context):
        try:
            instance_list = self.instance_var.resolve(context)
        except template.VariableDoesNotExist:
            return ''
        if len(instance_list) == 0:
            return ''
        ct = ContentType.objects.get_for_model(instance_list[0])
        annotations = Annotation.objects.filter(content_type=ct, object_id__in=[o.id for o in instance_list])\
            .select_related("user").order_by("object_id", "selection_start", "-selection_end")
        annotation_dict = {}
        for annotation in annotations:
            # Avoid multiple queries, so get object from given list
            annotation.content_object = find(lambda x: x.id == annotation.object_id, instance_list)
            annotation_dict.setdefault(annotation.object_id, {})
            annotation_dict[annotation.object_id].setdefault("annotations", []).append(annotation)
        for instance in instance_list:
            annotation_dict.setdefault(instance.id, {})["form"] = NewAnnotationForm(auto_id=False,
                initial={"content_type": ct.id, "object_id": instance.id})
        context[self.as_var] = annotation_dict
        return ""


text:
using System;

namespace HaloSharp.Model.Common
{
    [Serializable]
    public class ISO8601 : IEquatable<ISO8601>
    {
        // ReSharper disable once InconsistentNaming
        public DateTime? ISO8601Date { get; set; }

        public bool Equals(ISO8601 other)
        {
            if (ReferenceEquals(null, other))
            {
                return false;
            }

            if (ReferenceEquals(this, other))
            {
                return true;
            }

            return ISO8601Date.Equals(other.ISO8601Date);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            if (obj.GetType() != typeof(ISO8601))
            {
                return false;
            }

            return Equals((ISO8601)obj);
        }

        public override int GetHashCode()
        {
            return ISO8601Date.GetHashCode();
        }

        public static bool operator ==(ISO8601 left, ISO8601 right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(ISO8601 left, ISO8601 right)
        {
            return !Equals(left, right);
        }
    }
}


text:
/* $Id$ */

/*
 *  Copyright (c) 2003-2009 Axel Andersson
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <string.h>
#include <errno.h>
#include <wired/wired.h>

#include "accounts.h"
#include "events.h"
#include "files.h"
#include "index.h"
#include "main.h"
#include "server.h"
#include "settings.h"
#include "trackers.h"
#include "transfers.h"
#include "users.h"

#define WD_FILES_META_TYPE_PATH							".wired/type"
#define WD_FILES_META_COMMENTS_PATH						".wired/comments"
#define WD_FILES_META_PERMISSIONS_PATH					".wired/permissions"
#define WD_FILES_META_LABELS_PATH						".wired/labels"

#define WD_FILES_PERMISSIONS_FIELD_SEPARATOR			"\34"

#define WD_FILES_OLDSTYLE_COMMENT_FIELD_SEPARATOR		"\34"
#define WD_FILES_OLDSTYLE_COMMENT_SEPARATOR				"\35"

struct _wd_files_privileges {
	wi_runtime_base_t									base;

	wi_string_t											*owner;
	wi_string_t											*group;
	wi_uinteger_t										mode;
};

static void												wd_files_delete_path_callback(wi_string_t *);
static void												wd_files_move_path_copy_callback(wi_string_t *, wi_string_t *);
static void												wd_files_move_path_delete_callback(wi_string_t *);
static void												wd_files_move_thread(wi_runtime_instance_t *);

static void												wd_files_fsevents_thread(wi_runtime_instance_t *);
static void												wd_files_fsevents_callback(wi_string_t *);

static wi_string_t *									wd_files_comment(wi_string_t *);

static wi_string_t *									wd_files_drop_box_path_in_path(wi_string_t *, wd_user_t *);

static wd_files_privileges_t *							wd_files_privileges_alloc(void);
static wi_string_t *									wd_files_privileges_description(wi_runtime_instance_t *instance);
static void												wd_files_privileges_dealloc(wi_runtime_instance_t *);

static wd_files_privileges_t *							wd_files_privileges_with_string(wi_string_t *);
static wd_files_privileges_t *							wd_files_privileges_default_drop_box_privileges(void);

static wi_string_t *									wd_files_privileges_string(wd_files_privileges_t *);

static wi_runtime_id_t									wd_files_privileges_runtime_id = WI_RUNTIME_ID_NULL;
static wi_runtime_class_t								wd_files_privileges_runtime_class = {
	"wd_files_privileges_t",
	wd_files_privileges_dealloc,
	NULL,
	NULL,
	wd_files_privileges_description,
	NULL
};

wi_string_t												*wd_files;
wi_uinteger_t											wd_files_root_volume;
wi_fsevents_t											*wd_files_fsevents;

void wd_files_initialize(void) {
	wd_files_fsevents = wi_fsevents_init(wi_fsevents_alloc());

	if(wd_files_fsevents)
		wi_fsevents_set_callback(wd_files_fsevents, wd_files_fsevents_callback);
	else
		wi_log_warn(WI_STR("Could not create fsevents: %m"));

	wd_files_privileges_runtime_id = wi_runtime_register_class(&wd_files_privileges_runtime_class);
}

void wd_files_apply_settings(wi_set_t *changes) {
	wi_string_t			*realpath;
	wi_fs_stat_t		sb;

	wi_release(wd_files);
	wd_files = wi_retain(wi_config_path_for_name(wd_config, WI_STR("files")));

	realpath = wi_string_by_resolving_aliases_in_path(wd_files);

	if(wi_fs_stat_path(realpath, &sb))
		wd_files_root_volume = sb.dev;
}

void wd_files_schedule(void) {
	if(wd_files_fsevents) {
		if(!wi_thread_create_thread(wd_files_fsevents_thread, NULL))
			wi_log_error(WI_STR("Could not create an fsevents thread: %m"));
	}
}

#pragma mark -

wi_boolean_t wd_files_reply_list(wi_string_t *path, wi_boolean_t recursive, wd_user_t *user, wi_p7_message_t *message) {
	wi_p7_message_t				*reply;
	wi_string_t					*realpath, *filepath, *resolvedpath, *virtualpath;
	wi_fsenumerator_t			*fsenumerator;
	wd_account_t				*account;
	wd_files_privileges_t		*privileges;
	wi_fs_statfs_t				sfb;
	wi_fs_stat_t				dsb, sb, lsb;
	wi_fsenumerator_status_t	status;
	wi_file_offset_t			datasize, rsrcsize;
	wd_file_label_t				label;
	wi_uinteger_t				pathlength, depthlimit, directorycount;
	wd_file_type_t				type, pathtype;
	wi_boolean_t				root, upload, alias, readable, writable;
	uint32_t					device;

	root		= wi_is_equal(path, WI_STR("/"));
	realpath	= wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));
	account		= wd_user_account(user);
	pathtype	= wd_files_type(realpath);

	if(!wi_fs_stat_path(realpath, &dsb)) {
		wi_log_error(WI_STR("Could not read info for \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	depthlimit		= wd_account_file_recursive_list_depth_limit(account);
	fsenumerator	= wi_fs_enumerator_at_path(realpath);

	if(!fsenumerator) {
		wi_log_error(WI_STR("Could not open \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	pathlength = wi_string_length(realpath);

	if(pathlength == 1)
		pathlength--;

	while((status = wi_fsenumerator_get_next_path(fsenumerator, &filepath)) != WI_FSENUMERATOR_EOF) {
		if(status == WI_FSENUMERATOR_ERROR) {
			wi_log_error(WI_STR("Could not list \"%@\": %m"), filepath);

			continue;
		}

		if(depthlimit > 0 && wi_fsenumerator_level(fsenumerator) > depthlimit) {
			wi_fsenumerator_skip_descendents(fsenumerator);

			continue;
		}

		if(wi_fs_path_is_invisible(filepath)) {
			wi_fsenumerator_skip_descendents(fsenumerator);

			continue;
		}

		if(!recursive)
			wi_fsenumerator_skip_descendents(fsenumerator);

		virtualpath = wi_string_substring_from_index(filepath, pathlength);

		if(!root)
			virtualpath = wi_string_by_inserting_string_at_index(virtualpath, path, 0);

		alias = wi_fs_path_is_alias(filepath);

		if(alias)
			resolvedpath = wi_string_by_resolving_aliases_in_path(filepath);
		else
			resolvedpath = filepath;

		if(!wi_fs_lstat_path(resolvedpath, &lsb)) {
			wi_log_error(WI_STR("Could not read info for \"%@\": %m"), resolvedpath);

			continue;
		}

		if(!wi_fs_stat_path(resolvedpath, &sb))
			sb = lsb;

		readable	= false;
		writable	= false;
		type		= wd_files_type_with_stat(resolvedpath, &sb);

		if(type == WD_FILE_TYPE_DROPBOX) {
			privileges	= wd_files_drop_box_privileges(resolvedpath);
			readable	= wd_files_privileges_is_readable_by_account(privileges, account);
			writable	= wd_files_privileges_is_writable_by_account(privileges, account);
		}

		switch(type) {
			case WD_FILE_TYPE_DIR:
			case WD_FILE_TYPE_UPLOADS:
				datasize		= 0;
				rsrcsize		= 0;
				directorycount	= wd_files_count_path(resolvedpath, user, message);
				break;

			case WD_FILE_TYPE_DROPBOX:
				datasize		= 0;
				rsrcsize		= 0;
				directorycount	= readable ? wd_files_count_path(resolvedpath, user, message) : 0;
				break;

			case WD_FILE_TYPE_FILE:
			default:
				datasize		= sb.size;
				rsrcsize		= wi_fs_resource_fork_size_for_path(resolvedpath);
				directorycount	= 0;
				break;
		}

		label = wd_files_label(filepath);

		device = alias ? dsb.dev : sb.dev;

		if(device == wd_files_root_volume)
			device = 0;

		reply = wi_p7_message_init_with_name(wi_p7_message_alloc(), WI_STR("wired.file.file_list"), wd_p7_spec);
		wi_p7_message_set_string_for_name(reply, virtualpath, WI_STR("wired.file.path"));

		if(type == WD_FILE_TYPE_FILE) {
			wi_p7_message_set_uint64_for_name(reply, datasize, WI_STR("wired.file.data_size"));
			wi_p7_message_set_uint64_for_name(reply, rsrcsize, WI_STR("wired.file.rsrc_size"));
		} else {
			wi_p7_message_set_uint32_for_name(reply, directorycount, WI_STR("wired.file.directory_count"));
		}

		wi_p7_message_set_date_for_name(reply, wi_date_with_time(sb.birthtime), WI_STR("wired.file.creation_time"));
		wi_p7_message_set_date_for_name(reply, wi_date_with_time(sb.mtime), WI_STR("wired.file.modification_time"));
		wi_p7_message_set_enum_for_name(reply, type, WI_STR("wired.file.type"));
		wi_p7_message_set_bool_for_name(reply, alias || S_ISLNK(lsb.mode), WI_STR("wired.file.link"));
		wi_p7_message_set_bool_for_name(reply, (type == WD_FILE_TYPE_FILE && sb.mode & 0111), WI_STR("wired.file.executable"));
		wi_p7_message_set_enum_for_name(reply, label, WI_STR("wired.file.label"));
		wi_p7_message_set_uint32_for_name(reply, device, WI_STR("wired.file.volume"));

		if(type == WD_FILE_TYPE_DROPBOX) {
			wi_p7_message_set_bool_for_name(reply, readable, WI_STR("wired.file.readable"));
			wi_p7_message_set_bool_for_name(reply, writable, WI_STR("wired.file.writable"));
		}

		wd_user_reply_message(user, reply, message);
		wi_release(reply);

		if(recursive && (type == WD_FILE_TYPE_DROPBOX && !readable)) {
			wi_fsenumerator_skip_descendents(fsenumerator);

			continue;
		}
	}

	reply = wi_p7_message_with_name(WI_STR("wired.file.file_list.done"), wd_p7_spec);
	wi_p7_message_set_string_for_name(reply, path, WI_STR("wired.file.path"));

	if(pathtype == WD_FILE_TYPE_DROPBOX) {
		privileges	= wd_files_drop_box_privileges(realpath);
		readable	= wd_files_privileges_is_readable_by_account(privileges, account);
		writable	= wd_files_privileges_is_writable_by_account(privileges, account);

		wi_p7_message_set_bool_for_name(reply, readable, WI_STR("wired.file.readable"));
		wi_p7_message_set_bool_for_name(reply, writable, WI_STR("wired.file.writable"));
	} else {
		readable	= false;
		writable	= false;
	}

	if(wd_account_transfer_upload_anywhere(account))
		upload = true;
	else if(pathtype == WD_FILE_TYPE_DROPBOX)
		upload = writable;
	else if(pathtype == WD_FILE_TYPE_UPLOADS)
		upload = wd_account_transfer_upload_files(account);
	else
		upload = false;

	if(upload && wi_fs_statfs_path(realpath, &sfb))
		wi_p7_message_set_uint64_for_name(reply, (wi_file_offset_t) sfb.bavail * (wi_file_offset_t) sfb.frsize, WI_STR("wired.file.available"));
	else
		wi_p7_message_set_uint64_for_name(reply, 0, WI_STR("wired.file.available"));

	wd_user_reply_message(user, reply, message);

	return true;
}

wi_file_offset_t wd_files_count_path(wi_string_t *path, wd_user_t *user, wi_p7_message_t *message) {
	wi_mutable_string_t		*filepath;
	DIR						*dir;
	struct dirent			*de, *dep;
	wi_file_offset_t		count = 0;

	dir = opendir(wi_string_cstring(path));

	if(dir) {
		filepath	= wi_mutable_copy(path);
		de			= wi_malloc(sizeof(struct dirent) + WI_PATH_SIZE);

		wi_mutable_string_append_cstring(filepath, "/");

		while(readdir_r(dir, de, &dep) == 0 && dep) {
			if(dep->d_name[0] != '.') {
				wi_mutable_string_append_cstring(filepath, dep->d_name);

				if(!wi_fs_path_is_invisible(filepath))
					count++;

				wi_mutable_string_delete_characters_from_index(filepath, wi_string_length(filepath) - strlen(dep->d_name));
			}
		}

		wi_release(filepath);

		wi_free(de);

		closedir(dir);

		return count;
	} else {
		wi_log_error(WI_STR("Could not open \"%@\": %s"),
			path, strerror(errno));

		if(user)
			wd_user_reply_file_errno(user, message);

		return 0;
	}
}

wi_boolean_t wd_files_reply_info(wi_string_t *path, wd_user_t *user, wi_p7_message_t *message) {
	wi_p7_message_t			*reply;
	wi_string_t				*realpath, *parentpath, *comment;
	wd_account_t			*account;
	wd_files_privileges_t	*privileges = NULL;
	wi_file_offset_t		datasize, rsrcsize;
	wd_file_type_t			type;
	wi_fs_stat_t			dsb, sb, lsb;
	wd_file_label_t			label;
	wi_uinteger_t			directorycount;
	wi_boolean_t			alias, readable, writable;
	uint32_t				device;

	account			= wd_user_account(user);
	realpath		= wd_files_real_path(path, user);
	alias			= wi_fs_path_is_alias(realpath);
	realpath		= wi_string_by_resolving_aliases_in_path(realpath);
	parentpath		= wi_string_by_deleting_last_path_component(realpath);

	if(!wi_fs_stat_path(parentpath, &dsb)) {
		wi_log_error(WI_STR("Could not read info for \"%@\": %m"), parentpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	if(!wi_fs_lstat_path(realpath, &lsb)) {
		wi_log_error(WI_STR("Could not read info for \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	if(!wi_fs_stat_path(realpath, &sb))
		sb = lsb;

	readable	= false;
	writable	= false;
	type		= wd_files_type_with_stat(realpath, &sb);

	if(type == WD_FILE_TYPE_DROPBOX) {
		privileges	= wd_files_drop_box_privileges(realpath);
		readable	= wd_files_privileges_is_readable_by_account(privileges, account);
		writable	= wd_files_privileges_is_writable_by_account(privileges, account);
	}

	comment = wd_files_comment(realpath);

	switch(type) {
		case WD_FILE_TYPE_DIR:
		case WD_FILE_TYPE_UPLOADS:
			datasize		= 0;
			rsrcsize		= 0;
			directorycount	= wd_files_count_path(realpath, user, message);
			break;

		case WD_FILE_TYPE_DROPBOX:
			datasize		= 0;
			rsrcsize		= 0;
			directorycount	= readable ? wd_files_count_path(realpath, user, message) : 0;
			break;

		case WD_FILE_TYPE_FILE:
		default:
			datasize		= sb.size;
			rsrcsize		= wi_fs_resource_fork_size_for_path(realpath);
			directorycount	= 0;
			break;
	}

	label = wd_files_label(realpath);

	device = alias ? dsb.dev : sb.dev;

	if(device == wd_files_root_volume)
		device = 0;

	reply = wi_p7_message_with_name(WI_STR("wired.file.info"), wd_p7_spec);
	wi_p7_message_set_string_for_name(reply, path, WI_STR("wired.file.path"));
	wi_p7_message_set_enum_for_name(reply, type, WI_STR("wired.file.type"));

	if(type == WD_FILE_TYPE_FILE) {
		wi_p7_message_set_uint64_for_name(reply, datasize, WI_STR("wired.file.data_size"));
		wi_p7_message_set_uint64_for_name(reply, rsrcsize, WI_STR("wired.file.rsrc_size"));
	} else {
		wi_p7_message_set_uint32_for_name(reply, directorycount, WI_STR("wired.file.directory_count"));
	}

	wi_p7_message_set_date_for_name(reply, wi_date_with_time(sb.birthtime), WI_STR("wired.file.creation_time"));
	wi_p7_message_set_date_for_name(reply, wi_date_with_time(sb.mtime), WI_STR("wired.file.modification_time"));
	wi_p7_message_set_string_for_name(reply, comment, WI_STR("wired.file.comment"));
	wi_p7_message_set_bool_for_name(reply, (alias || S_ISLNK(lsb.mode)), WI_STR("wired.file.link"));
	wi_p7_message_set_bool_for_name(reply, (type == WD_FILE_TYPE_FILE && sb.mode & 0111), WI_STR("wired.file.executable"));
	wi_p7_message_set_enum_for_name(reply, label, WI_STR("wired.file.label"));
	wi_p7_message_set_uint32_for_name(reply, device, WI_STR("wired.file.volume"));

	if(type == WD_FILE_TYPE_DROPBOX) {
		wi_p7_message_set_string_for_name(reply, privileges->owner, WI_STR("wired.file.owner"));
		wi_p7_message_set_bool_for_name(reply, (privileges->mode & WD_FILE_OWNER_WRITE), WI_STR("wired.file.owner.write"));
		wi_p7_message_set_bool_for_name(reply, (privileges->mode & WD_FILE_OWNER_READ), WI_STR("wired.file.owner.read"));
		wi_p7_message_set_string_for_name(reply, privileges->group, WI_STR("wired.file.group"));
		wi_p7_message_set_bool_for_name(reply, (privileges->mode & WD_FILE_GROUP_WRITE), WI_STR("wired.file.group.write"));
		wi_p7_message_set_bool_for_name(reply, (privileges->mode & WD_FILE_GROUP_READ), WI_STR("wired.file.group.read"));
		wi_p7_message_set_bool_for_name(reply, (privileges->mode & WD_FILE_EVERYONE_WRITE), WI_STR("wired.file.everyone.write"));
		wi_p7_message_set_bool_for_name(reply, (privileges->mode & WD_FILE_EVERYONE_READ), WI_STR("wired.file.everyone.read"));
		wi_p7_message_set_bool_for_name(reply, readable, WI_STR("wired.file.readable"));
		wi_p7_message_set_bool_for_name(reply, writable, WI_STR("wired.file.writable"));
	}

	wd_user_reply_message(user, reply, message);

	return true;
}

wi_boolean_t wd_files_reply_preview(wi_string_t *path, wd_user_t *user, wi_p7_message_t *message) {
	wi_p7_message_t			*reply;
	wi_string_t				*realpath;
	wi_data_t				*data;
	wi_fs_stat_t			sb;

	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));

	if(!wi_fs_stat_path(realpath, &sb)) {
		wi_log_error(WI_STR("Could not preview \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	if(sb.size > (10 * 1024 * 1024) - (10 * 1024)) {
		wi_log_error(WI_STR("Could not preview \"%@\": Too large"), realpath);
		wd_user_reply_internal_error(user, WI_STR("File too large to preview"), message);

		return false;
	}

	data = wi_data_with_contents_of_file(realpath);

	if(!data) {
		wi_log_error(WI_STR("Could not preview \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	reply = wi_p7_message_with_name(WI_STR("wired.file.preview"), wd_p7_spec);
	wi_p7_message_set_string_for_name(reply, path, WI_STR("wired.file.path"));
	wi_p7_message_set_data_for_name(reply, data, WI_STR("wired.file.preview"));

	wd_user_reply_message(user, reply, message);

	return true;
}

wi_boolean_t wd_files_create_path(wi_string_t *path, wd_file_type_t type, wd_user_t *user, wi_p7_message_t *message) {
	wi_string_t		*realpath;

	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));

	if(!wi_fs_create_directory(realpath, 0777)) {
		wi_log_error(WI_STR("Could not create \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	if(type != WD_FILE_TYPE_DIR)
		wd_files_set_type(path, type, user, message);

	wd_index_add_file(realpath);

	return true;
}

wi_boolean_t wd_files_delete_path(wi_string_t *path, wd_user_t *user, wi_p7_message_t *message) {
	wi_mutable_string_t		*realpath;
	wi_string_t				*component;
	wi_boolean_t			result;

	realpath	= wi_autorelease(wi_mutable_copy(wd_files_real_path(path, user)));
	component	= wi_string_last_path_component(realpath);

	wi_mutable_string_delete_last_path_component(realpath);
	wi_mutable_string_resolve_aliases_in_path(realpath);
	wi_mutable_string_append_path_component(realpath, component);

	result = wi_fs_delete_path_with_callback(realpath, wd_files_delete_path_callback);

	if(result) {
		wd_files_remove_comment(path, NULL, NULL);
		wd_files_remove_label(path, NULL, NULL);
	} else {
		wi_log_error(WI_STR("Could not delete \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);
	}

	return result;
}

static void wd_files_delete_path_callback(wi_string_t *path) {
	wd_index_delete_file(path);
}

wi_boolean_t wd_files_move_path(wi_string_t *frompath, wi_string_t *topath, wd_user_t *user, wi_p7_message_t *message) {
	wi_array_t				*array;
	wi_mutable_string_t		*realfrompath, *realtopath;
	wi_string_t				*realfromname, *realtoname;
	wi_string_t				*path;
	wi_fs_stat_t			sb;
	wi_boolean_t			result = false;

	realfrompath	= wi_autorelease(wi_mutable_copy(wd_files_real_path(frompath, user)));
	realtopath		= wi_autorelease(wi_mutable_copy(wd_files_real_path(topath, user)));
	realfromname	= wi_string_last_path_component(realfrompath);
	realtoname		= wi_string_last_path_component(realtopath);

	wi_mutable_string_delete_last_path_component(realfrompath);
	wi_mutable_string_resolve_aliases_in_path(realfrompath);
	wi_mutable_string_append_path_component(realfrompath, realfromname);

	wi_mutable_string_delete_last_path_component(realtopath);
	wi_mutable_string_resolve_aliases_in_path(realtopath);
	wi_mutable_string_append_path_component(realtopath, realtoname);

	if(!wi_fs_lstat_path(realfrompath, &sb)) {
		wi_log_error(WI_STR("Could not read info for \"%@\": %m"), realfrompath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	if(wi_string_case_insensitive_compare(realfrompath, realtopath) == 0) {
		path = wi_fs_temporary_path_with_template(
			wi_string_with_format(WI_STR("%@/.%@.XXXXXXXX"),
				  wi_string_by_deleting_last_path_component(realfrompath),
				  wi_string_last_path_component(realfromname)));

		if(path) {
			result = wi_fs_rename_path(realfrompath, path);

			if(result)
				result = wi_fs_rename_path(path, realtopath);
		}
	} else {
		if(wi_fs_lstat_path(realtopath, &sb)) {
			wd_user_reply_error(user, WI_STR("wired.error.file_exists"), message);

			return false;
		}

		result = wi_fs_rename_path(realfrompath, realtopath);
	}

	if(result) {
		wd_files_move_comment(frompath, topath, user, message);
		wd_files_move_label(frompath, topath, user, message);

		wd_index_delete_file(realfrompath);
		wd_index_add_file(realtopath);
	} else {
		if(wi_error_code() == EXDEV) {
			array = wi_array_init_with_data(wi_array_alloc(),
				frompath,
				topath,
				realfrompath,
				realtopath,
				(void *) NULL);

			result = wi_thread_create_thread(wd_files_move_thread, array);

			if(!result) {
				wi_log_error(WI_STR("Could not create a copy thread: %m"));
				wd_user_reply_internal_error(user, wi_error_string(), message);
			}

			wi_release(array);
		} else {
			wi_log_error(WI_STR("Could not rename \"%@\" to \"%@\": %m"),
				realfrompath, realtopath);
			wd_user_reply_file_errno(user, message);
		}
	}

	return result;
}

static void wd_files_move_thread(wi_runtime_instance_t *argument) {
	wi_pool_t		*pool;
	wi_array_t		*array = argument;
	wi_string_t		*frompath, *topath, *realfrompath, *realtopath;

	pool			= wi_pool_init(wi_pool_alloc());
	frompath		= WI_ARRAY(array, 0);
	topath			= WI_ARRAY(array, 1);
	realfrompath	= WI_ARRAY(array, 2);
	realtopath		= WI_ARRAY(array, 3);

	if(wi_fs_copy_path_with_callback(realfrompath, realtopath, wd_files_move_path_copy_callback)) {
		wd_files_move_comment(frompath, topath, NULL, NULL);
		wd_files_move_label(frompath, topath, NULL, NULL);

		if(!wi_fs_delete_path_with_callback(realfrompath, wd_files_move_path_delete_callback))
			wi_log_error(WI_STR("Could not delete \"%@\": %m"), realfrompath);
	} else {
		wi_log_error(WI_STR("Could not copy \"%@\" to \"%@\": %m"), realfrompath, realtopath);
	}

	wi_release(pool);
}

static void wd_files_move_path_copy_callback(wi_string_t *frompath, wi_string_t *topath) {
	wd_index_add_file(topath);
}

static void wd_files_move_path_delete_callback(wi_string_t *path) {
	wd_index_delete_file(path);
}

wi_boolean_t wd_files_link_path(wi_string_t *frompath, wi_string_t *topath, wd_user_t *user, wi_p7_message_t *message) {
	wi_mutable_string_t		*realfrompath, *realtopath;
	wi_string_t				*realfromname, *realpath;
	wi_fs_stat_t			sb;

	realfrompath	= wi_autorelease(wi_mutable_copy(wd_files_real_path(frompath, user)));
	realtopath		= wi_autorelease(wi_mutable_copy(wd_files_real_path(topath, user)));
	realfromname	= wi_string_last_path_component(realfrompath);

	wi_mutable_string_delete_last_path_component(realfrompath);
	wi_mutable_string_resolve_aliases_in_path(realfrompath);
	wi_mutable_string_resolve_aliases_in_path(realtopath);
	wi_mutable_string_append_path_component(realfrompath, realfromname);

	if(!wi_fs_lstat_path(realfrompath, &sb)) {
		wi_log_error(WI_STR("Could not read info for \"%@\": %m"), realfrompath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	if(wi_fs_lstat_path(realtopath, &sb)) {
		wd_user_reply_error(user, WI_STR("wired.error.file_exists"), message);

		return false;
	}

	realpath = wi_fs_real_path_for_path(realfrompath);

	if(!realpath) {
		wi_log_error(WI_STR("Could not get real path for \"%@\": %m"), realfrompath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	if(!wi_fs_symlink_path(realpath, realtopath)) {
		wi_log_error(WI_STR("Could not symlink \"%@\" to \"%@\": %m"), realpath, realtopath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	wd_index_add_file(realtopath);

	return true;
}

#pragma mark -

#pragma mark -

static void wd_files_fsevents_thread(wi_runtime_instance_t *instance) {
	wi_pool_t		*pool;

	pool = wi_pool_init(wi_pool_alloc());

	while(true) {
		if(!wi_fsevents_run_with_timeout(wd_files_fsevents, 0.0))
			wi_log_error(WI_STR("Could not listen on fsevents: %m"));

		wi_pool_drain(pool);
	}

	wi_release(pool);
}

static void wd_files_fsevents_callback(wi_string_t *path) {
	wi_pool_t			*pool;
	wi_enumerator_t		*enumerator, *pathenumerator;
	wi_p7_message_t		*message;
	wi_string_t			*virtualpath;
	wd_user_t			*user;
	wi_boolean_t		exists, directory;

	pool = wi_pool_init(wi_pool_alloc());

	wi_retain(path);

	exists = (wi_fs_path_exists(path, &directory) && directory);

	wi_dictionary_rdlock(wd_users);

	enumerator = wi_dictionary_data_enumerator(wd_users);

	while((user = wi_enumerator_next_data(enumerator))) {
		if(wd_user_state(user) == WD_USER_LOGGED_IN && wi_set_contains_data(wd_user_subscribed_paths(user), path)) {
			pathenumerator = wi_array_data_enumerator(wd_user_subscribed_virtual_paths_for_path(user, path));

			while((virtualpath = wi_enumerator_next_data(pathenumerator))) {
				if(exists)
					message = wi_p7_message_with_name(WI_STR("wired.file.directory_changed"), wd_p7_spec);
				else
					message = wi_p7_message_with_name(WI_STR("wired.file.directory_deleted"), wd_p7_spec);

				wi_p7_message_set_string_for_name(message, virtualpath, WI_STR("wired.file.path"));
				wd_user_send_message(user, message);
			}

			if(!exists)
				wd_user_unsubscribe_path(user, path);
		}
	}

	wi_dictionary_unlock(wd_users);

	wi_release(path);
	wi_release(pool);
}

#pragma mark -

wi_boolean_t wd_files_set_type(wi_string_t *path, wd_file_type_t type, wd_user_t *user, wi_p7_message_t *message) {
	wi_string_t		*realpath, *metapath, *typepath;

	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));
	metapath = wi_string_by_appending_path_component(realpath, WI_STR(WD_FILES_META_PATH));
	typepath = wi_string_by_appending_path_component(realpath, WI_STR(WD_FILES_META_TYPE_PATH));

	if(type != WD_FILE_TYPE_DIR) {
		if(!wi_fs_create_directory(metapath, 0777)) {
			if(wi_error_code() != EEXIST) {
				wi_log_error(WI_STR("Could not create \"%@\": %m"), metapath);
				wd_user_reply_file_errno(user, message);

				return false;
			}
		}

		if(!wi_string_write_to_file(wi_string_with_format(WI_STR("%u\n"), type), typepath)) {
			wi_log_error(WI_STR("Could not write to \"%@\": %m"), typepath);
			wd_user_reply_file_errno(user, message);

			return false;
		}
	} else {
		if(!wi_fs_delete_path(typepath)) {
			wi_log_error(WI_STR("Could not delete \"%@\": %m"), typepath);
			wd_user_reply_file_errno(user, message);

			return false;
		}
	}

	return true;
}

wd_file_type_t wd_files_type(wi_string_t *path) {
	wi_fs_stat_t	sb;

	if(!wi_fs_stat_path(path, &sb))
		return WD_FILE_TYPE_FILE;

	return wd_files_type_with_stat(path, &sb);
}

wd_file_type_t wd_files_type_with_stat(wi_string_t *realpath, wi_fs_stat_t *sbp) {
	wi_string_t		*typepath, *string;
	wi_fs_stat_t	sb;
	wd_file_type_t	type;

	if(!S_ISDIR(sbp->mode))
		return WD_FILE_TYPE_FILE;

	typepath = wi_string_by_appending_path_component(realpath, WI_STR(WD_FILES_META_TYPE_PATH));

	if(!wi_fs_stat_path(typepath, &sb) || sb.size > 8)
		return WD_FILE_TYPE_DIR;

	string = wi_autorelease(wi_string_init_with_contents_of_file(wi_string_alloc(), typepath));

	if(!string)
		return WD_FILE_TYPE_DIR;

	type = wi_string_uint32(wi_string_by_deleting_surrounding_whitespace(string));

	if(type == WD_FILE_TYPE_FILE)
		type = WD_FILE_TYPE_DIR;

	return type;
}

#pragma mark -

wi_boolean_t wd_files_set_executable(wi_string_t *path, wi_boolean_t executable, wd_user_t *user, wi_p7_message_t *message) {
	wi_string_t		*realpath;

	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));

	if(!wi_fs_set_mode_for_path(realpath, executable ? 0755 : 0644)) {
		wi_log_error(WI_STR("Could not set mode for \"%@\": %m"), realpath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	return true;
}

#pragma mark -

static wi_string_t * wd_files_comment(wi_string_t *path) {
#ifdef HAVE_CORESERVICES_CORESERVICES_H
	return wi_fs_finder_comment_for_path(path);
#else
	wi_runtime_instance_t	*instance;
	wi_file_t				*file;
	wi_array_t				*array;
	wi_string_t				*name, *dirpath, *commentspath, *string;

	name			= wi_string_last_path_component(path);
	dirpath			= wi_string_by_deleting_last_path_component(path);
	commentspath	= wi_string_by_appending_path_component(dirpath, WI_STR(WD_FILES_META_COMMENTS_PATH));
	instance		= wi_plist_read_instance_from_file(commentspath);

	if(!instance || wi_runtime_id(instance) != wi_dictionary_runtime_id()) {
		file = wi_file_for_reading(commentspath);

		if(!file)
			return NULL;

		while((string = wi_file_read_to_string(file, WI_STR(WD_FILES_OLDSTYLE_COMMENT_SEPARATOR)))) {
			array = wi_string_components_separated_by_string(string, WI_STR(WD_FILES_OLDSTYLE_COMMENT_FIELD_SEPARATOR));

			if(wi_array_count(array) == 2 && wi_is_equal(WI_ARRAY(array, 0), name))
				return WI_ARRAY(array, 1);
		}
	}

	return wi_dictionary_data_for_key(instance, name);
#endif
}

wi_boolean_t wd_files_set_comment(wi_string_t *path, wi_string_t *comment, wd_user_t *user, wi_p7_message_t *message) {
	wi_runtime_instance_t	*instance;
#ifdef HAVE_CORESERVICES_CORESERVICES_H
	wi_string_t				*realpath;
#endif
	wi_string_t				*name, *dirpath, *realdirpath, *metapath, *commentspath;

	name			= wi_string_last_path_component(path);
	dirpath			= wi_string_by_deleting_last_path_component(path);
	realdirpath		= wi_string_by_resolving_aliases_in_path(wd_files_real_path(dirpath, user));
	metapath		= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_PATH));
	commentspath	= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_COMMENTS_PATH));

	if(!wi_fs_create_directory(metapath, 0777)) {
		if(wi_error_code() != EEXIST) {
			wi_log_error(WI_STR("Could not create \"%@\": %m"), metapath);

			if(user)
				wd_user_reply_file_errno(user, message);

			return false;
		}
	}

	instance = wi_plist_read_instance_from_file(commentspath);

	if(!instance || wi_runtime_id(instance) != wi_dictionary_runtime_id())
		instance = wi_mutable_dictionary();

	wi_mutable_dictionary_set_data_for_key(instance, comment, name);

	if(!wi_plist_write_instance_to_file(instance, commentspath)) {
		wi_log_error(WI_STR("Could not write to \"%@\": %m"), commentspath);

		if(user)
			wd_user_reply_file_errno(user, message);

		return false;
	}

#ifdef HAVE_CORESERVICES_CORESERVICES_H
	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));

	if(wi_fs_path_exists(realpath, NULL)) {
		if(!wi_fs_set_finder_comment_for_path(comment, realpath)) {
			wi_log_error(WI_STR("Could not set Finder comment: %m"));
			wd_user_reply_internal_error(user, wi_error_string(), message);

			return false;
		}
	}
#endif

	return true;
}

void wd_files_move_comment(wi_string_t *frompath, wi_string_t *topath, wd_user_t *user, wi_p7_message_t *message) {
	wi_string_t		*realfrompath, *comment;

	realfrompath	= wi_string_by_resolving_aliases_in_path(wd_files_real_path(frompath, user));
	comment			= wd_files_comment(realfrompath);

	if(comment) {
		wd_files_remove_comment(frompath, user, message);
		wd_files_set_comment(topath, comment, user, message);
	}
}

wi_boolean_t wd_files_remove_comment(wi_string_t *path, wd_user_t *user, wi_p7_message_t *message) {
	wi_runtime_instance_t	*instance;
#ifdef HAVE_CORESERVICES_CORESERVICES_H
	wi_string_t				*realpath;
#endif
	wi_string_t				*name, *dirpath, *realdirpath, *metapath, *commentspath;

	name			= wi_string_last_path_component(path);
	dirpath			= wi_string_by_deleting_last_path_component(path);
	realdirpath		= wi_string_by_resolving_aliases_in_path(wd_files_real_path(dirpath, user));
	metapath		= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_PATH));
	commentspath	= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_COMMENTS_PATH));

	instance = wi_plist_read_instance_from_file(commentspath);

	if(instance && wi_runtime_id(instance) != wi_dictionary_runtime_id()) {
		wi_mutable_dictionary_remove_data_for_key(instance, name);

		if(wi_dictionary_count(instance) > 0) {
			if(!wi_plist_write_instance_to_file(instance, commentspath)) {
				wi_log_error(WI_STR("Could not write to \"%@\": %m"), commentspath);

				if(user)
					wd_user_reply_file_errno(user, message);

				return false;
			}
		} else {
			if(!wi_fs_delete_path(commentspath)) {
				wi_log_error(WI_STR("Could not delete \"%@\": %m"), commentspath);

				if(user)
					wd_user_reply_file_errno(user, message);

				return false;
			}
		}
	}

#ifdef HAVE_CORESERVICES_CORESERVICES_H
	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));

	if(wi_fs_path_exists(realpath, NULL)) {
		if(!wi_fs_set_finder_comment_for_path(WI_STR(""), realpath)) {
			wi_log_error(WI_STR("Could not set Finder comment: %m"));
			wd_user_reply_internal_error(user, wi_error_string(), message);

			return false;
		}
	}
#endif

	return true;
}

#pragma mark -

wi_boolean_t wd_files_set_label(wi_string_t *path, wd_file_label_t label, wd_user_t *user, wi_p7_message_t *message) {
	wi_runtime_instance_t	*instance;
#ifdef HAVE_CORESERVICES_CORESERVICES_H
	wi_string_t				*realpath;
#endif
	wi_string_t				*name, *dirpath, *realdirpath, *metapath, *labelspath;

	name			= wi_string_last_path_component(path);
	dirpath			= wi_string_by_deleting_last_path_component(path);
	realdirpath		= wi_string_by_resolving_aliases_in_path(wd_files_real_path(dirpath, user));
	metapath		= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_PATH));
	labelspath		= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_LABELS_PATH));

	if(!wi_fs_create_directory(metapath, 0777)) {
		if(wi_error_code() != EEXIST) {
			wi_log_error(WI_STR("Could not create \"%@\": %m"), metapath);

			if(user)
				wd_user_reply_file_errno(user, message);

			return false;
		}
	}

	instance = wi_plist_read_instance_from_file(labelspath);

	if(!instance || wi_runtime_id(instance) != wi_dictionary_runtime_id())
		instance = wi_mutable_dictionary();

	wi_mutable_dictionary_set_data_for_key(instance, WI_INT32(label), name);

	if(!wi_plist_write_instance_to_file(instance, labelspath)) {
		wi_log_error(WI_STR("Could not write to \"%@\": %m"), labelspath);

		if(user)
			wd_user_reply_file_errno(user, message);

		return false;
	}

#ifdef HAVE_CORESERVICES_CORESERVICES_H
	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));

	if(wi_fs_path_exists(realpath, NULL)) {
		if(!wi_fs_set_finder_label_for_path(label, realpath)) {
			wi_log_error(WI_STR("Could not set Finder label: %m"));
			wd_user_reply_internal_error(user, wi_error_string(), message);

			return false;
		}
	}
#endif

	return true;
}

wd_file_label_t wd_files_label(wi_string_t *path) {
#ifdef HAVE_CORESERVICES_CORESERVICES_H
	return wi_fs_finder_label_for_path(path);
#else
	wi_runtime_instance_t	*instance;
	wi_number_t				*label;
	wi_string_t				*name, *dirpath, *labelspath;

	name			= wi_string_last_path_component(path);
	dirpath			= wi_string_by_deleting_last_path_component(path);
	labelspath		= wi_string_by_appending_path_component(dirpath, WI_STR(WD_FILES_META_LABELS_PATH));
	instance		= wi_plist_read_instance_from_file(labelspath);
	label			= instance ? wi_dictionary_data_for_key(instance, name) : NULL;

	return label ? wi_number_int32(label) : WD_FILE_LABEL_NONE;
#endif
}

wi_boolean_t wd_files_remove_label(wi_string_t *path, wd_user_t *user, wi_p7_message_t *message) {
	wi_runtime_instance_t	*instance;
#ifdef HAVE_CORESERVICES_CORESERVICES_H
	wi_string_t				*realpath;
#endif
	wi_string_t				*name, *dirpath, *realdirpath, *metapath, *labelspath;

	name			= wi_string_last_path_component(path);
	dirpath			= wi_string_by_deleting_last_path_component(path);
	realdirpath		= wi_string_by_resolving_aliases_in_path(wd_files_real_path(dirpath, user));
	metapath		= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_PATH));
	labelspath		= wi_string_by_appending_path_component(realdirpath, WI_STR(WD_FILES_META_LABELS_PATH));

	instance = wi_plist_read_instance_from_file(labelspath);

	if(instance && wi_runtime_id(instance) == wi_dictionary_runtime_id()) {
		wi_mutable_dictionary_remove_data_for_key(instance, name);

		if(wi_dictionary_count(instance) > 0) {
			if(!wi_plist_write_instance_to_file(instance, labelspath)) {
				wi_log_error(WI_STR("Could not write to \"%@\": %m"), labelspath);

				if(user)
					wd_user_reply_file_errno(user, message);

				return false;
			}
		} else {
			if(!wi_fs_delete_path(labelspath)) {
				wi_log_error(WI_STR("Could not delete \"%@\": %m"), labelspath);

				if(user)
					wd_user_reply_file_errno(user, message);

				return false;
			}
		}
	}

#ifdef HAVE_CORESERVICES_CORESERVICES_H
	realpath = wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));

	if(wi_fs_path_exists(realpath, NULL)) {
		if(!wi_fs_set_finder_label_for_path(WI_FS_FINDER_LABEL_NONE, realpath)) {
			wi_log_error(WI_STR("Could not remove Finder label: %m"));
			wd_user_reply_internal_error(user, wi_error_string(), message);

			return false;
		}
	}
#endif

	return true;
}

void wd_files_move_label(wi_string_t *frompath, wi_string_t *topath, wd_user_t *user, wi_p7_message_t *message) {
	wi_string_t			*realfrompath;
	wd_file_label_t		label;

	realfrompath	= wi_string_by_resolving_aliases_in_path(wd_files_real_path(frompath, user));
	label			= wd_files_label(realfrompath);

	if(label != WD_FILE_LABEL_NONE) {
		wd_files_remove_label(frompath, user, message);
		wd_files_set_label(topath, label, user, message);
	}
}

#pragma mark -

wi_boolean_t wd_files_set_privileges(wi_string_t *path, wd_files_privileges_t *privileges, wd_user_t *user, wi_p7_message_t *message) {
	wi_string_t		*realpath, *metapath, *permissionspath;
	wi_string_t		*string;

	realpath			= wi_string_by_resolving_aliases_in_path(wd_files_real_path(path, user));
	metapath			= wi_string_by_appending_path_component(realpath, WI_STR(WD_FILES_META_PATH));
	permissionspath		= wi_string_by_appending_path_component(realpath, WI_STR(WD_FILES_META_PERMISSIONS_PATH));

	if(!wi_fs_create_directory(metapath, 0777)) {
		if(wi_error_code() != EEXIST) {
			wi_log_error(WI_STR("Could not create \"%@\": %m"), metapath);
			wd_user_reply_file_errno(user, message);

			return false;
		}
	}

	string = wd_files_privileges_string(privileges);

	if(!wi_string_write_to_file(string, permissionspath)) {
		wi_log_error(WI_STR("Could not write to \"%@\": %m"), permissionspath);
		wd_user_reply_file_errno(user, message);

		return false;
	}

	return true;
}

wd_files_privileges_t * wd_files_privileges(wi_string_t *path, wd_user_t *user) {
	wi_string_t				*realpath;

	realpath = wd_files_drop_box_path_in_path(path, user);

	if(!realpath)
		return NULL;

	return wd_files_drop_box_privileges(realpath);
}

wd_files_privileges_t * wd_files_drop_box_privileges(wi_string_t *path) {
	wi_string_t				*permissionspath, *string;
	wd_files_privileges_t	*privileges;
	wi_fs_stat_t			sb;

	permissionspath = wi_string_by_appending_path_component(path, WI_STR(WD_FILES_META_PERMISSIONS_PATH));

	if(!wi_fs_stat_path(permissionspath, &sb))
		return wd_files_privileges_default_drop_box_privileges();

	if(sb.size > 128) {
		wi_log_error(WI_STR("Could not read \"%@\": Size is too large (%u"), permissionspath, sb.size);

		return wd_files_privileges_default_drop_box_privileges();
	}

	string = wi_autorelease(wi_string_init_with_contents_of_file(wi_string_alloc(), permissionspath));

	if(!string) {
		wi_log_error(WI_STR("Could not read \"%@\": %m"), permissionspath);

		return wd_files_privileges_default_drop_box_privileges();
	}

	privileges = wd_files_privileges_with_string(string);

	if(!privileges) {
		wi_log_error(WI_STR("Could not read \"%@\": Contents is malformed (\"%@\")"), permissionspath, string);

		return wd_files_privileges_default_drop_box_privileges();
	}

	return privileges;
}

#pragma mark -

wi_boolean_t wd_files_path_is_valid(wi_string_t *path) {
	if(wi_string_has_prefix(path, WI_STR(".")))
		return false;

	if(wi_string_contains_string(path, WI_STR("/.."), 0))
        return false;

	if(wi_string_contains_string(path, WI_STR("../"), 0))
        return false;

	return true;
}

wi_string_t * wd_files_virtual_path(wi_string_t *path, wd_user_t *user) {
	wi_string_t		*accountpath, *virtualpath;
	wd_account_t	*account;

	account			= user ? wd_user_account(user) : NULL;
	accountpath		= account ? wd_account_files(account) : NULL;

	if(accountpath && wi_string_length(accountpath) > 0)
		virtualpath = wi_string_by_normalizing_path(wi_string_with_format(WI_STR("%@/%@"), wd_account_files(account), path));
	else
		virtualpath = path;

	return virtualpath;
}

wi_string_t * wd_files_real_path(wi_string_t *path, wd_user_t *user) {
	wi_string_t		*accountpath, *realpath;
	wd_account_t	*account;

	account			= user ? wd_user_account(user) : NULL;
	accountpath		= account ? wd_account_files(account) : NULL;

	if(accountpath && wi_string_length(accountpath) > 0)
		realpath = wi_string_with_format(WI_STR("%@/%@/%@"), wd_files, wd_account_files(account), path);
	else
		realpath = wi_string_with_format(WI_STR("%@/%@"), wd_files, path);

	return realpath;
}

wi_boolean_t wd_files_has_uploads_or_drop_box_in_path(wi_string_t *path, wd_user_t *user, wd_files_privileges_t **privileges) {
	wi_mutable_string_t		*realpath;
	wi_string_t				*dirpath;
	wi_array_t				*array;
	wi_uinteger_t			i, count;

	realpath	= wi_autorelease(wi_mutable_copy(wi_string_by_resolving_aliases_in_path(wd_files_real_path(WI_STR("/"), user))));
	dirpath		= wi_string_by_deleting_last_path_component(path);
	array		= wi_string_path_components(dirpath);
	count		= wi_array_count(array);

	for(i = 0; i < count; i++) {
		wi_mutable_string_append_path_component(realpath, WI_ARRAY(array, i));

		switch(wd_files_type(realpath)) {
			case WD_FILE_TYPE_UPLOADS:
				*privileges = NULL;

				return true;
				break;

			case WD_FILE_TYPE_DROPBOX:
				*privileges = wd_files_drop_box_privileges(path);

				return true;
				break;

			default:
				break;
		}
	}

	*privileges = NULL;

	return false;
}

#pragma mark -

static wi_string_t * wd_files_drop_box_path_in_path(wi_string_t *path, wd_user_t *user) {
	wi_mutable_string_t		*realpath;
	wi_array_t				*array;
	wi_uinteger_t			i, count;

	realpath	= wi_autorelease(wi_mutable_copy(wi_string_by_resolving_aliases_in_path(wd_files_real_path(WI_STR("/"), user))));
	array		= wi_string_path_components(path);
	count		= wi_array_count(array);

	for(i = 0; i < count; i++) {
		wi_mutable_string_append_path_component(realpath, WI_ARRAY(array, i));

		if(wd_files_type(realpath) == WD_FILE_TYPE_DROPBOX)
			return realpath;
	}

	return NULL;
}

#pragma mark -

wi_string_t * wd_files_string_for_bytes(wi_file_offset_t bytes) {
	double						kb, mb, gb, tb, pb;

	if(bytes < 1024)
		return wi_string_with_format(WI_STR("%llu bytes"), bytes);

	kb = (double) bytes / 1024.0;

	if(kb < 1024.0)
		return wi_string_with_format(WI_STR("%.1f KB"), kb);

	mb = (double) kb / 1024.0;

	if(mb < 1024.0)
		return wi_string_with_format(WI_STR("%.1f MB"), mb);

	gb = (double) mb / 1024.0;

	if(gb < 1024.0)
		return wi_string_with_format(WI_STR("%.1f GB"), gb);

	tb = (double) gb / 1024.0;

	if(tb < 1024.0)
		return wi_string_with_format(WI_STR("%.1f TB"), tb);

	pb = (double) tb / 1024.0;

	if(pb < 1024.0)
		return wi_string_with_format(WI_STR("%.1f PB"), pb);

	return NULL;
}

#pragma mark -

static wd_files_privileges_t * wd_files_privileges_alloc(void) {
	return wi_runtime_create_instance(wd_files_privileges_runtime_id, sizeof(wd_files_privileges_t));
}

static wi_string_t * wd_files_privileges_description(wi_runtime_instance_t *instance) {
	wd_files_privileges_t		*privileges = instance;

	return wi_string_with_format(WI_STR("<%@ %p>{owner = \"%@\" %c%c, group = \"%@\" %c%c, everyone = %c%c}"),
		wi_runtime_class_name(privileges),
		privileges,
		privileges->owner,
		(privileges->mode & WD_FILE_OWNER_READ) ? 'r' : '-',
		(privileges->mode & WD_FILE_OWNER_WRITE) ? 'w' : '-',
		privileges->group,
		(privileges->mode & WD_FILE_GROUP_READ) ? 'r' : '-',
		(privileges->mode & WD_FILE_GROUP_WRITE) ? 'w' : '-',
		(privileges->mode & WD_FILE_EVERYONE_READ) ? 'r' : '-',
		(privileges->mode & WD_FILE_EVERYONE_WRITE) ? 'w' : '-');
}

static void wd_files_privileges_dealloc(wi_runtime_instance_t *instance) {
	wd_files_privileges_t		*privileges = instance;

	wi_release(privileges->owner);
	wi_release(privileges->group);
}

#pragma mark -

wd_files_privileges_t * wd_files_privileges_with_message(wi_p7_message_t *message) {
	wd_files_privileges_t		*privileges;
	wi_p7_boolean_t				value;

	privileges = wd_files_privileges_alloc();
	privileges->owner = wi_retain(wi_p7_message_string_for_name(message, WI_STR("wired.file.owner")));

	if(!privileges->owner)
		privileges->owner = wi_retain(WI_STR(""));

	privileges->group = wi_retain(wi_p7_message_string_for_name(message, WI_STR("wired.file.group")));

	if(!privileges->group)
		privileges->group = wi_retain(WI_STR(""));

	if(wi_p7_message_get_bool_for_name(message, &value, WI_STR("wired.file.owner.read")) && value)
		privileges->mode |= WD_FILE_OWNER_READ;

	if(wi_p7_message_get_bool_for_name(message, &value, WI_STR("wired.file.owner.write")) && value)
		privileges->mode |= WD_FILE_OWNER_WRITE;

	if(wi_p7_message_get_bool_for_name(message, &value, WI_STR("wired.file.group.read")) && value)
		privileges->mode |= WD_FILE_GROUP_READ;

	if(wi_p7_message_get_bool_for_name(message, &value, WI_STR("wired.file.group.write")) && value)
		privileges->mode |= WD_FILE_GROUP_WRITE;

	if(wi_p7_message_get_bool_for_name(message, &value, WI_STR("wired.file.everyone.read")) && value)
		privileges->mode |= WD_FILE_EVERYONE_READ;

	if(wi_p7_message_get_bool_for_name(message, &value, WI_STR("wired.file.everyone.write")) && value)
		privileges->mode |= WD_FILE_EVERYONE_WRITE;

	return wi_autorelease(privileges);
}

static wd_files_privileges_t * wd_files_privileges_with_string(wi_string_t *string) {
	wi_array_t				*array;
	wd_files_privileges_t	*privileges;

	string				= wi_string_by_deleting_surrounding_whitespace(string);
	array				= wi_string_components_separated_by_string(string, WI_STR(WD_FILES_PERMISSIONS_FIELD_SEPARATOR));

	if(wi_array_count(array) != 3)
		return NULL;

	privileges			= wd_files_privileges_alloc();
	privileges->owner	= wi_retain(WI_ARRAY(array, 0));
	privileges->group	= wi_retain(WI_ARRAY(array, 1));
	privileges->mode	= wi_string_uint32(WI_ARRAY(array, 2));

	return wi_autorelease(privileges);
}

static wd_files_privileges_t * wd_files_privileges_default_drop_box_privileges(void) {
	wd_files_privileges_t	*privileges;

	privileges			= wd_files_privileges_alloc();
	privileges->owner	= wi_retain(WI_STR(""));
	privileges->group	= wi_retain(WI_STR(""));
	privileges->mode	= WD_FILE_EVERYONE_WRITE;

	return wi_autorelease(privileges);
}

#pragma mark -

static wi_string_t * wd_files_privileges_string(wd_files_privileges_t *privileges) {
	return wi_string_with_format(WI_STR("%#@%s%#@%s%u"),
	   privileges->owner,		WD_FILES_PERMISSIONS_FIELD_SEPARATOR,
	   privileges->group,		WD_FILES_PERMISSIONS_FIELD_SEPARATOR,
	   privileges->mode);
}

wi_boolean_t wd_files_privileges_is_readable_by_account(wd_files_privileges_t *privileges, wd_account_t *account) {
	if(privileges->mode & WD_FILE_EVERYONE_READ)
		return true;

	if(wd_account_file_access_all_dropboxes(account))
		return true;

	if(privileges->mode & WD_FILE_GROUP_READ && wi_string_length(privileges->group) > 0) {
		if(wi_is_equal(privileges->group, wd_account_group(account)) || wi_array_contains_data(wd_account_groups(account), privileges->group))
			return true;
	}

	if(privileges->mode & WD_FILE_OWNER_READ && wi_string_length(privileges->owner) > 0) {
		if(wi_is_equal(privileges->owner, wd_account_name(account)))
			return true;
	}

	return false;
}

wi_boolean_t wd_files_privileges_is_writable_by_account(wd_files_privileges_t *privileges, wd_account_t *account) {
	if(privileges->mode & WD_FILE_EVERYONE_WRITE)
		return true;

	if(wd_account_file_access_all_dropboxes(account))
		return true;

	if(privileges->mode & WD_FILE_GROUP_WRITE && wi_string_length(privileges->group) > 0) {
		if(wi_is_equal(privileges->group, wd_account_group(account)) || wi_array_contains_data(wd_account_groups(account), privileges->group))
			return true;
	}

	if(privileges->mode & WD_FILE_OWNER_WRITE && wi_string_length(privileges->owner) > 0) {
		if(wi_is_equal(privileges->owner, wd_account_name(account)))
			return true;
	}

	return false;
}

wi_boolean_t wd_files_privileges_is_readable_and_writable_by_account(wd_files_privileges_t *privileges, wd_account_t *account) {
	if(privileges->mode & WD_FILE_EVERYONE_READ && privileges->mode & WD_FILE_EVERYONE_WRITE)
		return true;

	if(wd_account_file_access_all_dropboxes(account))
		return true;

	if(privileges->mode & WD_FILE_GROUP_READ && privileges->mode & WD_FILE_GROUP_WRITE && wi_string_length(privileges->group) > 0) {
		if(wi_is_equal(privileges->group, wd_account_group(account)) || wi_array_contains_data(wd_account_groups(account), privileges->group))
			return true;
	}

	if(privileges->mode & WD_FILE_OWNER_READ && privileges->mode & WD_FILE_OWNER_WRITE && wi_string_length(privileges->owner) > 0) {
		if(wi_is_equal(privileges->owner, wd_account_name(account)))
			return true;
	}

	return false;
}


text:
all:
	g++ -D _TEST_FLAGGER PeachyFlagger.cpp -o testFlagger
	g++ -D _TEST_PEACHYSTEPPER PeachyStepper.cpp -o testStepper
flagger:
	g++ -D _TEST_FLAGGER PeachyFlagger.cpp -o testFlagger
stepper:
	g++ -D _TEST_PEACHYSTEPPER PeachyStepper.cpp -o testStepper


text:
START_ATF_NAMESPACE
    typedef IEnumOleUndoUnits *LPENUMOLEUNDOUNITS;
END_ATF_NAMESPACE


text:
/**
 * Returns an singular version of the passed word.
 *
 * TODO: Common problem - should be replaced with some package.
 *
 * @param  {String} plural Plural version of the word
 * @return {String}        Singular version of the word
 */
const singularize = function(plural) {
  if (s.endsWith(plural, 'ies')) {
    return plural.substr(0, plural.length - 3) + 'y';
  } else if (s.endsWith(plural, 'es')) {
    return plural.substr(0, plural.length - 2);
  } else if (s.endsWith(plural, 's')) {
    return plural.substr(0, plural.length - 1);
  }
  return plural;
};

/**
 * Rails like models using ES6.
 *
 * Smart Record gives you the ability to:
 *
 * - Represent models and their data.
 * - Represent associations between these models.
 * - Represent inheritance hierarchies through related models.
 * - Validate models before they get persisted to the database.
 * - Perform database operations in an object-oriented fashion.
 */
SmartModel = class SmartModel {
  /**
   * Creates an new model instance.
   * @param  {Object}     attrs Initial attributes.
   * @return {SmartModel}       Returns new model instance.
   */
  constructor(attrs = {}) {
    let defaultAttrs = this.constructor.defaultScope;
    if (_.isArray(defaultAttrs)) {
      for (const defaultAttr of defaultAttrs) {
        this.extend(defaultAttr);
      }
    } else if (_.isObject(defaultAttrs)) {
      this.extend(defaultAttrs);
    }
    this._applyDefaults();
    this.extend(attrs);
    this._validationContext = this.constructor.simpleSchema().newContext();
    this._initBelongsToRelations();
    this._initHasManyRelations();
    this._initHasOneRelations();
    const idAlias = s.decapitalize(this.constructor.modelName) + 'Id';
    Object.defineProperty(this, idAlias, {
      get: () => this.id
    });
  }

  /**
   * Initialize Model.
   *
   * Add default scopes based on Schema.
   */
  static init() {
    const schema = this.getSchema();
    schema.id = {type: String};
    const checkedTypes = [String];
    const rangeTypes = [Number, Date];
    for (const column in schema) {
      const type = schema[column].type;

      {
        const scope = 'with' + s.capitalize(column);
        this[scope] = (val) => {
          if (_.contains(checkedTypes, type)) {
            check(val, type);
          }
          return this.scope({
            selector: {[column]: val},
          });
        };
      }
      if (_.contains(rangeTypes, type)) {

      }
    }
  }

  static get collections() {
    if (!_.isObject(SmartModel._collections)) {
      SmartModel._collections = {};
    }
    return SmartModel._collections;
  }

  /**
   * Defines if the created Collection is local.
   *
   * @return {Boolean} Return true to create an unmanaged local collection.
   */
  static get localCollection() {
    return false;
  }

  /**
   * Defines the connection to the Meteor Server.
   *
   * @return {Object} Returns connection to Meteor server.
   */
  static get connection() {
    return this.localCollection === true ? null : undefined;
  }

  /**
   * Private: Used to check if Model is inherited from SmartModel.
   *
   * @return {Boolean} Returns true.
   */
  static get __isSmartModel() {
    return true;
  }

  /**
   * Private: Initialize belongsTo relation.
   *
   * Sets the getter and setter for the model instance.
   */
  _initBelongsToRelations() {
    const relations = this.constructor.belongsTo;
    if (_.isObject(relations)) {
      for (let name in relations) {
        const relation = relations[name];
        const foreignKey = (relation.foreignKey || name) + 'Id';
        const model = this.constructor._getModel(relation, name);
        if (model === undefined) {
          continue;
        }
        Object.defineProperty(this, name, {
          get: () => {
            if (this[foreignKey] !== undefined && this[foreignKey] !== null) {
              return model.find({_id: this[foreignKey]}, relation.options);
            } else {
              return null;
            }
          },
          set: (obj) => {
            this[foreignKey] = obj.id;
            if (relation.polymorphic === true) {
              this[(relation.foreignKey || name) + 'Model'] = obj.constructor.modelName;
            }
            return obj;
          }
        });
      }
    }
  }

  /**
   * Private: Initialize hasMany relation.
   *
   * Creates scopes to the related models.
   */
  _initHasManyRelations() {
    const relations = this.constructor.hasMany;
    if (_.isObject(relations)) {
      for (let name in relations) {
        const relation = relations[name];
        const foreignKey = (relation.foreignKey || s.decapitalize(this.constructor.modelName) + 'Id');
        const model = this.constructor._getModel(relation, name);
        if (model === null) {
          continue;
        }
        Object.defineProperty(this, name, {
          get: () => {
            return this.constructor.scope({
              model: model.name,
              selector: {
                [foreignKey]: this.id
              },
            });
          },
          writeable: false,
        });
      }
    }
  }

  /**
   * Private: Initialize hasOne relation.
   *
   * Creates getter for related models.
   */
  _initHasOneRelations() {
    const relations = this.constructor.hasOne;
    if (_.isObject(relations)) {
      for (let name in relations) {
        const relation = relations[name];
        const foreignKey = (relation.foreignKey || s.decapitalize(this.constructor.modelName) + 'Id');
        const model = this.constructor._getModel(relation, name);
        if (model === null) {
          continue;
        }
        Object.defineProperty(this, name, {
          get: () => {
            const query = {};
            query[foreignKey] = this.id;
            return model.find(query);
          },
          writeable: false,
          // settings needs an inverseOf option to unset the belongsTo
          // on the model side or destroy model if belongsTo is required
          // set: (obj) => {
          // }
        });
      }
    }
  }

  /**
   * An array of properties which are allowed
   * to pass but not passed to the database.
   *
   * @return {Array} Array of property names.
   */
  static get attrAccessors() {
    return [];
  }

  /**
   * Private: Returns model either from relation or fallback.
   *
   * @param  {Object}     relation Relation provided by belongsTo, hasMany or hasOne.
   * @param  {String}     fallback Name of an model which can be used as fallback.
   *
   * @return {SmartModel}          Returns an SmartModel.
   */
  static _getModel(relation, fallback) {
    let model = null;
    if (relation.polymorphic === true) {
      model = global[s.classify(singularize(relation.foreignKey || fallback) + 'Model')];
    } else if (_.isString(relation.model)) {
      model = global[relation.model];
    } else if (_.isObject(relation.model)) {
      model = relation.model;
    } else {
      model = global[s.classify(singularize(fallback))];
    }
    if (model !== null && model.__isSmartModel !== true) {
      return null;
    }
    return model;
  }

  /**
   * Private: Transforms minimongo results to SmartModel instances.
   *
   * @param  {Object} options Passed options get modified.
   */
  static _applyDefaultTransform(options) {
    if (options.transform === undefined) {
      options.transform = (attrs) => {
        return new this(attrs);
      }
    }
  }

  /**
   * Private: Sorts the records by creation if no sort is present.
   *
   * @param  {Object} options Passed options get modified.
   */
  static _applyDefaultSort(options) {
    if (options.sort === undefined) {
      options.sort = {createdAt: 1};
    }
  }

  /**
   * Prepare the passed relation and return all dependend destroy relations names.
   *
   * @param  {Object} relations Either an hasOne or hasMany relation is expected
   * @return {Array}            Returns an array of relation names
   */
  static _getDependentDestroy(relations) {
    const dependentDestroy = [];
    if (_.isObject(relations)) {
      for (const name in relations) {
        const relation = relations[name];
        if (_.isString(relation.dependent)) {
          relation.dependent = [relation.dependent];
        }
        if (_.isArray(relation.dependent) && _.contains(relation.dependent, 'destroy')) {
          dependentDestroy.push(name);
        }
      }
    }
    return dependentDestroy;
  }

  /**
   * Get all relation dependent relation names from has one which should be destroyed.
   *
   * @return {Array} Returns an array of relation names
   */
  static get _hasOneDependentDestroy() {
    return this._getDependentDestroy(this.hasOne);
  }

  /**
   * Get all relation dependent relation names from has many which should be destroyed.
   *
   * @return {Array} Returns an array of relation names
   */
  static get _hasManyDependentDestroy() {
    return this._getDependentDestroy(this.hasMany);

  }

  /**
   * Private: Returns an selector which takes care of the own default scope.
   * Also sets the alias names for the id to _id.
   *
   * @param  {Object} selector Selector to filter records.
   * @return {Object}          Merged Selector.
   */
  static _buildSelector(selector) {
    const defaultScope = this.defaultScope;
    const idAlias = s.decapitalize(this.modelName) + 'Id';
    if (_.isObject(selector) && selector._id === undefined &&
      (_.isString(selector.id) || _.isString(selector[idAlias]))) {
      selector._id = selector.id || selector[idAlias];
    }
    delete selector.id;
    delete selector[idAlias];
    return {$and: _.compact(_.flatten([defaultScope, selector]))};
  }

  /**
   * Returns the name of the model.
   *
   * This works even for scopes where the name of the Class
   * is not working cause scopes return anonymous Classes.
   *
   * @return {String} Returns the name of the model.
   */
  static get modelName() {
    return this._modelName || this.name;
  }

  /**
   * This function is used the create scopes.
   *
   * The selector can be passed by options.selector.
   *
   * @param  {Object}     options Pass the selector as part of the options.
   * @return {SmartModel}         An scope returns an anonymous model based on
   *                              its own model with an merged default scope.
   */
  static scope(options) {
    const model = this._getModel(options, this.modelName);
    let defScope = this.defaultScope;
    if (_.isArray(defScope)) {
      defScope = defScope.union(options.selector);
    } else if (_.isObject(defScope)) {
      defScope = [options.selector, defScope];
    } else {
      defScope = options.selector;
    }

    const klass = class extends model {
      static get defaultScope() {
        return defScope;
      }

      static get _modelName() {
        return model.modelName;
      }
    };

    klass.init();

    return klass;
  }

  /**
   * Returns the default scope.
   *
   * All search requests with find, where, all, first and last take care
   * of the default scope.
   *
   * @return {Object} Selector which is used while searching.
   */
  static get defaultScope() {
    return null;
  }

  /**
   * Returns one record which matches the passes selector.
   *
   * @param  {Object} selector Selector to filter records.
   * @param  {Object} options  Options to set for example the sort of the records.
   * @return {Object}          Returns an instance of SmartModel.
   */
  static find(selector = {}, options = {}) {
    this._applyDefaultSort(options);
    this._applyDefaultTransform(options);
    return this.collection.findOne(this._buildSelector(selector), options);
  }

  /**
   * Queries the database with the passed selector and options.
   *
   * Returns an cursor for reactive queries to the database.
   *
   * @param  {Object}        selector Selector to filter records.
   * @param  {Object}        options  Options to set for example the sort of the records.
   * @return {Meteor.Cursor}          Returns an Meteor Cursor to fetch instances of SmartModel.
   */
  static cursor(selector = {}, options = {}) {
    this._applyDefaultSort(options);
    this._applyDefaultTransform(options);
    return this.collection.find(this._buildSelector(selector), options);
  }

  /**
   * Queries the database with the passed selector and options.
   *
   * Returns an Array of model instances which match the selector.
   *
   * @param  {Object} selector Selector to filter records.
   * @param  {Object} options  Options to set for example the sort of the records.
   * @return {Array}           Returns an Array of model instances which match the selector.
   */
  static where(selector, options) {
    return this.cursor(selector, options).fetch();
  }

  /**
   * Fetches the count of matching items with selector and options.
   *
   * @param  {Object} selector Selector to filter records.
   * @param  {Object} options  Options to set for example the sort of the records.
   * @return {Number}          Returns the amount of matching records.
   */
  static count(selector, options) {
    return this.cursor(selector, options).count();
  }

  /**
   * Checks if there is any matching item with selector.
   *
   * @param  {Object}  selector Selector to filter records.
   * @return {Boolean}          Returns true if there is an matching record.
   */
  static hasAny(selector) {
    return this.cursor(selector).count() > 0;
  }

  /**
   * Checks if there is no matching item with selector.
   *
   * @param  {Object}  selector Selector to filter records.
   * @return {Boolean}          Returns true if there is no matching record.
   */
  static isEmpty(selector) {
    return this.cursor(selector).count() === 0;
  }

  /**
   * Fetches the first created record matching selector and options.
   *
   * @param  {Object} selector Selector to filter records.
   * @param  {Object} options  Options.
   * @return {Object}          Returns an model instance.
   */
  static first(selector, options = {}) {
    options.sort = {createdAt: 1};
    return this.find(selector, options);
  }

  /**
   * Fetches the latest created record matching selector and options.
   *
   * @param  {Object} selector Selector to filter records.
   * @param  {Object} options  Options.
   * @return {Object}          Returns an model instance.
   */
  static last(selector, options = {}) {
    options.sort = {createdAt: -1};
    return this.find(selector, options);
  }

  /**
   * Iterator to use `for (instance of Model)`
   *
   * @return {Iterator} Returns an Iterator to loop over model.
   */
  static [Symbol.iterator]() {
    return this.all()[Symbol.iterator]();
  }

  /**
   * Queries the database and return all records which matches the current scope.
   *
   * @param  {Object} selector Selector to filter records.
   * @param  {Object} options  Options to set for example the sort of the records.
   * @return {Array}           Returns an Array of model instances.
   */
  static all(options) {
    return this.where({}, options);
  }

  /**
   * Destroys all records which match the selector and options.
   *
   * @param  {Object} selector Selector to filter records.
   * @param  {Object} options  Options to set for example an limit of records.
   */
  static destroyAll(selector, skipCallbacks) {
    const models = this.where(selector);
    models.map(x => x.destroy(skipCallbacks));
  }

  /**
   * Get schema filled with defaults and belongsTo relations.
   *
   * @return {Object} Schema.
   */
  static getSchema(withTimestamps = false) {
    const schema = this.schema;
    if (!_.isObject(schema)) {
      return {};
    }
    if (withTimestamps === true) {
      schema.createdAt = {type: Number, optional: true};
      schema.updatedAt = {type: Number, optional: true};
    }
    const relations = this.belongsTo;
    if (_.isObject(relations)) {
      for (const name in relations) {
        const relation = relations[name];
        if (!_.isBoolean(relation.optional)) {
          relation.optional = false;
        }
        const foreignKey = (relation.foreignKey || name) + 'Id';
        schema[foreignKey] = {type: String, optional: relation.optional, defaultValue: null};
      }
    }
    return schema;
  }

  /**
   * Overrideable: Get collection name.
   *
   * @return {Strign} Returns custom name of the collection.
   */
  static get collectionName() {
    return null;
  }

  static simpleSchema(withTimestamps) {
    return new SimpleSchema(this.getSchema(withTimestamps));
  }

  /**
   * Overrideable: Schema definition of accepted propierties with
   * optional type and restrictions.
   *
   * @return {Object} Returns custom schema.
   */
  static get schema() {
    return {};
  }

  /**
   * Overriceable: Definition of all belongsTo relations.
   *
   * @return {Object} Returns custom relations.
   */
  static get belongsTo() {
    return {};
  }

  /**
   * Overriceable: Definition of all hasMany relations.
   *
   * @return {Object} Returns custom relations.
   */
  static get hasMany() {
    return {};
  }

  /**
   * Overriceable: Definition of all hasOne relations.
   *
   * @return {Object} Returns custom relations.
   */
  static get hasOne() {
    return {};
  }

  /**
   * Initialize new model instance.
   *
   * @param  {Object} attrs Apply passed attributes to the created instance.
   * @return {Object}       Returns model instance.
   */
  static build(attrs) {
    return new this(attrs);
  }

  /**
   * Initialize and try to save new model instance.
   *
   * @param  {Object} attrs Apply passed attributes to the created instance.
   * @return {Object}       Returns model instance.
   */
  static create(attrs) {
    modelInstance = this.build(attrs);
    return modelInstance.save();
  }

  /**
   * Returns or existing collection with the collection name
   * returned by the collectionName function.
   *
   * @return {Meteor.Collection} Returns Meteor.Collection instance.
   */
  static get collection() {
    const name = this.collectionName || this.modelName;
    return this.collections[name] ?
      this.collections[name] :
      this.collections[name] = new Mongo.Collection(name, {connection: this.connection});
  }

  /**
   * Allow users to write directly to this collection from client code,
   * subject to limitations you define.
   *
   * @param  {Object} options insert, update, remove functions that look at
   *                          a proposed modification to the database and
   *                          return true if it should be allowed.
   */
  static allow(options) {
    return this.collection.allow(options);
  }

  /**
   * Override allow rules.
   *
   * @param  {Object} options insert, update, remove functions that look at
   *                          a proposed modification to the database and
   *                          return true if it should be denied.
   */
  static deny(options) {
    return this.collection.deny(options);
  }

  /**
   * Apply the default valus from schema.
   */
  _applyDefaults() {
    const schema = this.constructor.getSchema()
    const attrs = this.attributes({add: [], addAttrAccessors: false});
    this.constructor.simpleSchema().clean(attrs);
    for (let column in schema) {
      if (this[column] === undefined && attrs[column] !== undefined) {
        this[column] = attrs[column];
      }
    }
  }

  /**
   * Executes the function with the passed name,
   * unless it was skipped.
   *
   * @param  {String}  name          Name of the callback function.
   * @param  {Array}   skipCallbacks Set this to true to skip all callbacks.
   *                                 Pass an Array of callback names which
   *                                 should be skipped.
   * @return {Boolean}               If the callback was skipped this function
   *                                 retruns true, else the result of the callback.
   */
  _executeCallback(name, skipCallbacks) {
    if (skipCallbacks === true || !_.isFunction(this[name]) ||
      (_.isArray(skipCallbacks) && _.contains(skipCallbacks, name))) {
      return true;
    }
    return this[name]();
  }

  /**
   * Insert arguments do not contain an id
   *
   * @return {Object} Returns the models attributes without id.
   */
  _insertAttributes() {
    return this.attributes({
      add: ['createdAt', 'updatedAt'],
      addAttrAccessors: false,
    });
  }

  /**
   * Update arguments do not contain an id or creation date.
   *
   * @return {Object} Returns the models attributes without id and createdAt.
   */
  _updateAttributes() {
    return this.attributes({
      add: ['updatedAt'],
      addAttrAccessors: false,
    });
  }

  /**
   * Check if the last validation passed or if there are errors.
   * If false is returned check errors for more details what failed.
   *
   * @return {Boolean} Returns if the last validation created errors.
   */
  get isValid() {
    return this.errors.length === 0;
  }

  /**
   * The current status of the record if its already
   * stored at the database.
   *
   * @return {Boolean} Returns true if record is not yet saved.
   */
  get isNew() {
    return this.id === undefined;
  }

  /**
   * The current status of the record if its already
   * stored at the database.
   *
   * @return {Boolean} Returns true if record is saved.
   */
  get isPersistent() {
    return !this.isNew;
  }

  /**
   * Shorthand readonly field to get _id property.
   *
   * @return {String} Returns the same value as _id.
   */
  get id() {
    return this._id;
  }

  /**
   * Get the name of the base class.
   *
   * @return {String} Returns the name of the base class where
   *                  the model is inherited from.
   */
  get itemType() {
    return this.constructor.modelName;
  }

  /**
   * Errors are set by `save`, `create`, `update` and `valudate`.
   * After you called one of this functions you can read the validation errors.
   *
   * @return {Array} Returns an array of objects with the
   *                 properties `column` and `error`
   */
  get errors() {
    return this._errors || [];
  }

  /**
   * Builds an simplte object (not an model instance) of all
   * model properties. You can reduce the number of fields by
   * passing the `pick` property with the options.
   *
   * @param  {Object} options Pass property pick as array of columns
   *                          which should be skipped while building.
   * @return {Object}         Returns simple object (not a model instance)
   *                          with all model properties.
   */
  attributes(options = {}) {
    let {pick, add, addAttrAccessors} = options;
    if (!_.isArray(add)) {
      add = [
        'createdAt',
        'updatedAt',
        'id',
      ];
    }
    if (!_.isBoolean(addAttrAccessors)) {
      addAttrAccessors = true
    }
    if (addAttrAccessors === true) {
      add = _.union(add, this.constructor.attrAccessors);
    }
    let columns = _.keys(this.constructor.getSchema());
    columns = _.union(columns, add);
    if (_.isArray(pick)) {
      columns = _.intersection(columns, pick);
    }

    return _.pick(this, columns);
  }

  /**
   * Run all validations against the columns to check if the current
   * state is valid or not.
   *
   * @param  {Array}   skipCallbacks Set to true to skip all callbacks, or pass
   *                                 an array with all callback names which should
   *                                 be skipped.
   *                                 Possible callbacks are:
   *                                 `beforeValidation`, `afterValidation`
   * @return {Boolean}               Returns if the current model state ist valid or not.
   *                                 Returns false if the validation is stopped by
   *                                 any callback.
   */
  validate(skipCallbacks = false) {
    if (this._executeCallback('beforeValidation', skipCallbacks) === false) {
      return false;
    }
    const attrs = this.attributes({add: [], addAttrAccessors: false});
    this._validationContext.validate(attrs);
    this._errors = this._validationContext.invalidKeys();
    if (this._executeCallback('afterValidation', skipCallbacks) === false) {
      return false;
    }
    return this.isValid;
  }

  /**
   * Sets the timestamps `createdAt` and `updatedAt`.
   * `updatedAt` is allways set, `createdAt` only if not yet present.
   *
   * @return {Object} Returns current model instance.
   */
  touch() {
    if (this.createdAt === undefined) {
      this.createdAt = Date.now();
    }
    this.updatedAt = Date.now();
    return this;
  }

  /**
   * Syncs the current record with the database.
   * Inserts an document if the record is new.
   * Updates the existing record if the record is persistent.
   *
   * @param  {Object}  options                Pass options to skip some actions
   * @param  {Boolean} options.skipValidation Set this to true if you want to allow that
   *                                          an invalid record is saved to the database.
   *                                          (Please handle this with care)
   * @param  {Array}   options.skipCallbacks  Set to true to skip all callbacks, or pass
   *                                          an array with all callback names which should
   *                                          be skipped.
   *                                          Possible callbacks are:
   *                                          `beforeValidation`, `afterValidation`,
   *                                          `beforeSave`, `beforeInsert`, `beforeUpdate`,
   *                                          `afterSave`, `afterInsert`, `afterUpdate`
   * @param  {Boolean} options.skiptouch      Set this to true if you do not want to update
   *                                          the timestamps `createdAt` and `updatedAt`
   * @param  {Boolean} options.skipApplyDefaults Set this to true if you do not want to set
   *                                             undefined properties from the schema to
   *                                             their default values.
   * @return {Object}                         On the server and model instance is returned.
   *                                          If the validation failed the model is initialized.
   *                                          Returns an Promise with the model on the client
   */
  save(options = {}) {
    const {skipValidation, skipCallbacks, skipTouch, skipApplyDefaults} = options;
    this._errors = [];
    let isValid = true
    if (skipApplyDefaults !== true) {
      this._applyDefaults();
    }
    if (skipValidation !== true) {
      isValid = this.validate(skipCallbacks);
    }
    if (this._executeCallback('beforeCommit', skipCallbacks) === false) {
      throw new Meteor.Error('Stopped by beforeCommit');
    }
    if (this._executeCallback('beforeSave', skipCallbacks) === false) {
      throw new Meteor.Error('Stopped by beforeSave');
    }
    if (isValid) {
      if (skipTouch !== true) {
        this.touch();
      }
      if (this.isNew) {
        // insert
        if (this._executeCallback('beforeInsert', skipCallbacks) === false) {
          throw new Meteor.Error('Stopped by beforeInsert');
        }
        const attrs = this._insertAttributes();
        if (Meteor.isServer) {
          this._id = this.constructor.collection.insert(attrs);
          this._executeCallback('afterInsert', skipCallbacks);
          this._executeCallback('afterSave', skipCallbacks);
          this._executeCallback('afterCommit', skipCallbacks);
          return this;
        } else {
          return new Promise((resolve, reject) => {
            this.constructor.collection.insert(attrs, (err, id) => {
              if (err) {
                this._errors = [err];
                reject(this);
              } else {
                this._id = id;
                this._executeCallback('afterInsert', skipCallbacks);
                this._executeCallback('afterSave', skipCallbacks);
                this._executeCallback('afterCommit', skipCallbacks);
                return resolve(this);
              }
            });
          });
        }
      } else {
        // update
        if (this._executeCallback('beforeUpdate', skipCallbacks) === false) {
          throw new Meteor.Error('Stopped by beforeUpdate');
        }
        const attrs = this._updateAttributes();
        if (Meteor.isServer) {
          this.constructor.collection.update({
            _id: this.id
          }, {
            $set: attrs
          });
          this._executeCallback('afterUpdate', skipCallbacks);
          this._executeCallback('afterSave', skipCallbacks);
          this._executeCallback('afterCommit', skipCallbacks);
          return this;
        } else {
          return new Promise((resolve, reject) => {
            this.constructor.collection.update({
              _id: this.id
            }, {
              $set: attrs
            }, (err, docs) => {
              if (err) {
                this._errors = [err];
                reject(this);
              } else {
                this._executeCallback('afterUpdate', skipCallbacks);
                this._executeCallback('afterSave', skipCallbacks);
                this._executeCallback('afterCommit', skipCallbacks);
                return resolve(this);
              }
            });
          });
        }
      }
    }
    if (Meteor.isServer) {
      return this;
    } else {
      return new Promise((resolve, reject) => {
        reject(this);
      });
    }
  }

  /**
   * Removed the current model instance from the database.
   *
   * @param  {Array}   skipCallbacks Set to true to skip all callbacks, or pass
   *                                 an array with all callback names which should
   *                                 be skipped.
   *                                 Possible callbacks are:
   *                                 `beforeDestroy`, `afterDestroy`
   * @return {Object}                Returns the current model instance.
   */
  destroy(skipCallbacks) {
    if (this._executeCallback('beforeCommit', skipCallbacks) === false) {
      throw new Meteor.Error('Stopped by beforeCommit');
    }
    if (this._executeCallback('beforeDestroy', skipCallbacks) === false) {
      throw new Meteor.Error('Stopped by beforeDestroy');
    }
    for (const relationName of this.constructor._hasOneDependentDestroy) {
      if (_.isObject(this[relationName])) {
        this[relationName].destroy();
      }
    }
    for (const relationName of this.constructor._hasManyDependentDestroy) {
      this[relationName].all().map(model => model.destroy());
    }
    this.constructor.collection.remove(this.id);
    delete this._id;
    this._executeCallback('afterDestroy', skipCallbacks);
    this._executeCallback('afterCommit', skipCallbacks);
    return this;
  }

  /**
   * Set multiple attributes by passing an object.
   * Only applies properties which are defined at the schema.
   *
   * @param  {Object} attrs Attributes which should be applied.
   * @return {Object}       Returns the current model instance.
   */
  extend(attrs) {
    let columns = _.keys(this.constructor.getSchema(true));
    columns = _.union(columns, this.constructor.attrAccessors);
    _.extend(this, _.pick(attrs, columns));
    return this;
  }

  /**
   * Sets multiple attributes by passing an object,
   * and saves the record afterwards.
   *
   * @param  {Object} attrs   Attributes which should be applied.
   * @param  {Object} options Theese options are passed to the save function.
   *                          See SmartModel#save() for more details.
   * @return {Object}         On the server and model instance is returned.
   *                          If the validation failed the model is initialized.
   *                          Returns an Promise with the model on the client
   */
  update(attrs, options) {
    this.extend(attrs);
    return this.save(options);
  }
}