nestene 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +31 -0
  7. data/Rakefile +2 -0
  8. data/TODO.txt +7 -0
  9. data/api.rb +8 -0
  10. data/autons.rb +13 -0
  11. data/bin/nestene.rb +5 -0
  12. data/config.ru +21 -0
  13. data/features/api/api_create_auton.feature +6 -0
  14. data/features/api/api_credentials.feature +11 -0
  15. data/features/api/api_list_of_autons.feature +6 -0
  16. data/features/callback.feature +13 -0
  17. data/features/create_auton.feature +11 -0
  18. data/features/credentials.feature +7 -0
  19. data/features/execute_step.feature +59 -0
  20. data/features/restart.feature +7 -0
  21. data/features/schedule_steps.feature +7 -0
  22. data/features/step_definitions/api/api_create_auton_steps.rb +16 -0
  23. data/features/step_definitions/api/api_credentials_steps.rb +22 -0
  24. data/features/step_definitions/api/api_list_of_autons_steps.rb +8 -0
  25. data/features/step_definitions/callback_steps.rb +75 -0
  26. data/features/step_definitions/create_auton_steps.rb +32 -0
  27. data/features/step_definitions/credentials_steps.rb +24 -0
  28. data/features/step_definitions/execute_step_steps.rb +177 -0
  29. data/features/step_definitions/restart_steps.rb +5 -0
  30. data/features/step_definitions/schedule_steps_steps.rb +29 -0
  31. data/features/step_definitions/time_delayed_steps_steps.rb +103 -0
  32. data/features/step_definitions/ui/create_auton_steps.rb +21 -0
  33. data/features/step_definitions/ui/list_of_autons_steps.rb +18 -0
  34. data/features/step_definitions/ui/schedule_auton_step_steps.rb +64 -0
  35. data/features/step_definitions/ui/show_auton_steps.rb +49 -0
  36. data/features/support/env.rb +36 -0
  37. data/features/time_delayed_steps.feature +28 -0
  38. data/features/ui/create_auton.feature +7 -0
  39. data/features/ui/list_of_autons.feature +7 -0
  40. data/features/ui/schedule_auton_step.feature +12 -0
  41. data/features/ui/show_auton.feature +14 -0
  42. data/lib/nestene/actor/auton_queue.rb +87 -0
  43. data/lib/nestene/actor/auton_storage.rb +50 -0
  44. data/lib/nestene/actor/core.rb +150 -0
  45. data/lib/nestene/actor/delayed_scheduler.rb +76 -0
  46. data/lib/nestene/auton_context.rb +31 -0
  47. data/lib/nestene/auton_execution_queue.rb +43 -0
  48. data/lib/nestene/auton_state.rb +27 -0
  49. data/lib/nestene/callback.rb +7 -0
  50. data/lib/nestene/delayed_method.rb +17 -0
  51. data/lib/nestene/executed_method.rb +29 -0
  52. data/lib/nestene/executing_method.rb +23 -0
  53. data/lib/nestene/execution_error.rb +39 -0
  54. data/lib/nestene/scheduled_method.rb +21 -0
  55. data/lib/nestene/storage.rb +72 -0
  56. data/lib/nestene/ui/app.rb +103 -0
  57. data/lib/nestene/ui/public/app/application.js +75 -0
  58. data/lib/nestene/ui/public/vendor/angular-ui-sortable/sortable.min.js +1 -0
  59. data/lib/nestene/ui/public/vendor/angular.min.js +248 -0
  60. data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap-theme.css +347 -0
  61. data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap-theme.min.css +7 -0
  62. data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap.css +5785 -0
  63. data/lib/nestene/ui/public/vendor/bootstrap/css/bootstrap.min.css +7 -0
  64. data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  65. data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +229 -0
  66. data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  67. data/lib/nestene/ui/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  68. data/lib/nestene/ui/public/vendor/bootstrap/js/bootstrap.js +1951 -0
  69. data/lib/nestene/ui/public/vendor/bootstrap/js/bootstrap.min.js +6 -0
  70. data/lib/nestene/ui/public/vendor/jquery-ui.min.js +13 -0
  71. data/lib/nestene/ui/public/vendor/jquery.bootstrap-growl.min.js +1 -0
  72. data/lib/nestene/ui/public/vendor/jquery.min.js +5 -0
  73. data/lib/nestene/ui/public/vendor/json-editor/directives.js +245 -0
  74. data/lib/nestene/ui/public/vendor/json-editor/styles.css +165 -0
  75. data/lib/nestene/ui/views/auton.haml +42 -0
  76. data/lib/nestene/ui/views/auton_form.haml +8 -0
  77. data/lib/nestene/ui/views/index.haml +35 -0
  78. data/lib/nestene/ui/views/layout.haml +40 -0
  79. data/lib/nestene/ui/views/schedule_step.haml +14 -0
  80. data/lib/nestene/version.rb +3 -0
  81. data/lib/nestene.rb +50 -0
  82. data/nestene.gemspec +39 -0
  83. data/spec/nestene/auton_execution_queue_spec.rb +78 -0
  84. data/spec/nestene/nestene_spec.rb +137 -0
  85. data/spec/nestene/storage_spec.rb +125 -0
  86. data/spec/spec_helper.rb +2 -0
  87. metadata +373 -0
@@ -0,0 +1,76 @@
1
+ require 'celluloid'
2
+
3
+ module Nestene
4
+ module Actor
5
+ class DelayedScheduler
6
+
7
+ include Celluloid
8
+ include Celluloid::Notifications
9
+
10
+ def initialize
11
+ subscribe('state_update', :update_schedule)
12
+ @schedule = []
13
+ @timer = nil
14
+ end
15
+
16
+
17
+ def update_schedule topic, auton_id, state
18
+
19
+ @schedule.delete_if{|e| e.first == auton_id}
20
+
21
+ state.queue.delayed.each do |d|
22
+ @schedule << [auton_id, d]
23
+ end
24
+
25
+ @schedule.sort_by!{|e| e[1].execute_at}
26
+
27
+ async.schedule_methods
28
+
29
+ end
30
+
31
+
32
+ def schedule_methods
33
+
34
+ if @timer
35
+ @timer.cancel
36
+ end
37
+
38
+ now = Time.now
39
+
40
+ loop do
41
+ (auton_id, delayed_method) = @schedule.shift
42
+
43
+ if auton_id
44
+ execute_in = delayed_method.execute_at - now
45
+ if execute_in <= 0
46
+ Celluloid::Actor["storage:%s" % auton_id].update do |state|
47
+ delayed = state.queue.delayed.shift
48
+ execute_in = delayed.execute_at - now
49
+ if execute_in <= 0
50
+ method = ScheduledMethod.new(delayed)
51
+ state.queue.to_execute << method
52
+ end
53
+ if delayed.every
54
+ delayed.execute_at += delayed.every
55
+ state.queue.add_delayed(delayed)
56
+ end
57
+ end
58
+ else
59
+ @schedule.unshift [auton_id, delayed_method]
60
+ break
61
+ end
62
+ else
63
+ break
64
+ end
65
+ end
66
+
67
+ unless @schedule.empty?
68
+ to_wait = @schedule.first[1].execute_at - now
69
+ @timer = after(to_wait){async.schedule_methods}
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ module Nestene
2
+ class AutonContext
3
+
4
+ def initialize auton_id
5
+ @auton_id = auton_id
6
+ end
7
+
8
+ attr_accessor :steps_to_schedule
9
+
10
+ def schedule_callback auton_id, method, parameters, callback_method
11
+ Celluloid::Actor[:nestene_core].schedule_step auton_id, method, parameters, @auton_id, callback_method
12
+ end
13
+
14
+ def schedule_step name, parameters=[]
15
+ Celluloid::Actor[:nestene_core].schedule_step @auton_id, name, parameters
16
+ end
17
+
18
+ def schedule_delayed_step delay, name, parameters=[]
19
+ Celluloid::Actor[:nestene_core].schedule_delayed_step @auton_id, delay, name, parameters
20
+ end
21
+
22
+ def schedule_repeating_delayed_step every, delay, name, parameters=[]
23
+ Celluloid::Actor[:nestene_core].schedule_repeating_delayed_step @auton_id, every, delay, name, parameters
24
+ end
25
+
26
+ def credentials
27
+ Celluloid::Actor[:nestene_core].get_credentials
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ require 'nestene/delayed_method'
2
+ require 'securerandom'
3
+ require 'structure_mapper'
4
+
5
+ module Nestene
6
+ class AutonExecutionQueue
7
+ include StructureMapper::Hash
8
+
9
+ def initialize
10
+ self.to_execute = []
11
+ self.executed = []
12
+ self.delayed = []
13
+ end
14
+
15
+ def add_delayed_method name, parameters, delay, every = nil
16
+ delayed_method = DelayedMethod.new
17
+ delayed_method.uuid = SecureRandom.uuid
18
+ delayed_method.name = name
19
+ delayed_method.parameters = parameters
20
+ delayed_method.execute_at = Time.now + delay
21
+ delayed_method.every = every
22
+ add_delayed(delayed_method)
23
+ delayed_method
24
+ end
25
+
26
+ def add_delayed delayed_method
27
+ delayed << delayed_method
28
+ delayed.sort_by! {|x| x.execute_at}
29
+ end
30
+
31
+
32
+ def remove_delayed_method method_uuid
33
+ delayed.delete_if{|m| m.uuid == method_uuid}
34
+ end
35
+
36
+ attribute failed: Boolean
37
+ attribute to_execute: [ScheduledMethod]
38
+ attribute executed: [ExecutedMethod]
39
+ attribute currently_executing: ExecutingMethod
40
+ attribute delayed: [DelayedMethod]
41
+
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ module Nestene
2
+ class AutonState
3
+
4
+ include StructureMapper::Hash
5
+
6
+ def initialize
7
+ self.queue=AutonExecutionQueue.new
8
+ end
9
+
10
+ def state
11
+ if queue.currently_executing == nil
12
+ queue.failed ? :failed : :ready
13
+ else
14
+ :running
15
+ end
16
+ end
17
+
18
+ attribute type: String
19
+ attribute serialized: {}
20
+ attribute queue: AutonExecutionQueue
21
+
22
+ def type_as_class
23
+ self.type ? Nestene::class_from_string(self.type) : nil
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,7 @@
1
+ module Nestene
2
+ class Callback
3
+ include StructureMapper::Hash
4
+ attribute name: Symbol
5
+ attribute auton_id: String
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module Nestene
2
+ class DelayedMethod
3
+ include StructureMapper::Hash
4
+
5
+ def initialize
6
+ self.parameters = []
7
+ self.scheduled_at = Time.now
8
+ end
9
+
10
+ attribute name: Symbol
11
+ attribute parameters: []
12
+ attribute uuid: String
13
+ attribute scheduled_at: Time
14
+ attribute execute_at: Time
15
+ attribute every: Float
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module Nestene
2
+ class ExecutedMethod
3
+ include StructureMapper::Hash
4
+
5
+ def initialize(executing_method = nil)
6
+ self.parameters = []
7
+ if executing_method
8
+ self.name = executing_method.name
9
+ self.parameters = executing_method.parameters
10
+ self.uuid = executing_method.uuid
11
+ self.scheduled_at = executing_method.scheduled_at
12
+ self.started_at = executing_method.started_at
13
+ self.duration = Time.now - executing_method.started_at
14
+ self.callback = executing_method.callback
15
+ end
16
+ end
17
+
18
+ attribute name: Symbol
19
+ attribute parameters: []
20
+ attribute uuid: String
21
+ attribute result: {}
22
+ attribute error: Exception
23
+ attribute scheduled_at: Time
24
+ attribute started_at: Time
25
+ attribute duration: Float
26
+ attribute callback: Callback
27
+
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module Nestene
2
+ class ExecutingMethod
3
+ include StructureMapper::Hash
4
+
5
+ def initialize(scheduled_method = nil)
6
+ if scheduled_method
7
+ self.name = scheduled_method.name
8
+ self.parameters = scheduled_method.parameters
9
+ self.uuid = scheduled_method.uuid
10
+ self.scheduled_at = scheduled_method.scheduled_at
11
+ self.started_at = Time.now
12
+ self.callback = scheduled_method.callback
13
+ end
14
+ end
15
+
16
+ attribute name: Symbol
17
+ attribute parameters: []
18
+ attribute uuid: String
19
+ attribute scheduled_at: Time
20
+ attribute started_at: Time
21
+ attribute callback: Callback
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ module Nestene
2
+
3
+ class ExecutionError
4
+ include StructureMapper::Hash
5
+
6
+ def initialize(exception = nil)
7
+
8
+ if exception
9
+ self.message = exception.message
10
+ self.type = exception.class.name
11
+ self.backtrace = exception.backtrace
12
+ end
13
+ end
14
+
15
+ attribute message: String
16
+ attribute type: String
17
+ attribute backtrace: [String]
18
+
19
+
20
+ def to_exception
21
+ exception = Nestene.class_from_string(type).new(message)
22
+ exception.set_backtrace self.backtrace
23
+ exception
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ class Exception
30
+
31
+ def to_structure
32
+ Nestene::ExecutionError.new(self).to_structure
33
+ end
34
+
35
+ def self.from_structure structure
36
+ structure ? Nestene::ExecutionError.from_structure(structure).to_exception : nil
37
+ end
38
+
39
+ end
@@ -0,0 +1,21 @@
1
+ module Nestene
2
+ class ScheduledMethod
3
+ include StructureMapper::Hash
4
+
5
+ def initialize(delayed_method = nil)
6
+ self.parameters = []
7
+ self.scheduled_at = Time.now
8
+ if delayed_method
9
+ self.uuid = delayed_method.uuid
10
+ self.name = delayed_method.name
11
+ self.parameters = delayed_method.parameters
12
+ end
13
+ end
14
+
15
+ attribute name: Symbol
16
+ attribute parameters: []
17
+ attribute uuid: String
18
+ attribute scheduled_at: Time
19
+ attribute callback: Callback
20
+ end
21
+ end
@@ -0,0 +1,72 @@
1
+ require 'json'
2
+
3
+ module Nestene
4
+
5
+
6
+ class FileNameConverter
7
+
8
+ def key_to_file_name(key)
9
+ key.gsub(/[^a-zA-Z0-9]/){|x| "_%x" % x.ord}
10
+ end
11
+
12
+ def file_name_to_key file_name
13
+ file_name.gsub(/_[a-zA-Z0-9]{2}/){|x| x[1..-1].hex.chr}
14
+ end
15
+
16
+ end
17
+
18
+ class DiskStorage
19
+
20
+ def initialize(dir)
21
+ @dir=dir
22
+ @converter = FileNameConverter.new
23
+ end
24
+
25
+ def list
26
+ entries = Dir.entries(@dir)
27
+ entries.delete('.')
28
+ entries.delete('..')
29
+ entries.delete('__credentials__')
30
+ entries.map{|fn| @converter.file_name_to_key(fn)}
31
+ end
32
+
33
+ def store(key, value)
34
+ final_name = "%s/%s" % [@dir,@converter.key_to_file_name(key)]
35
+ tmp_name = "%s.temp" % final_name
36
+ File.open(tmp_name,"w") do |f|
37
+ f.write(JSON.pretty_generate(value))
38
+ end
39
+ File.rename(tmp_name, final_name)
40
+ end
41
+
42
+ def load(key)
43
+ file_name = "%s/%s" % [@dir,@converter.key_to_file_name(key)]
44
+ if File.exist?(file_name)
45
+ File.open(file_name,"r"){|f| JSON.parse(f.read)}
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ class MemoryStorage
54
+
55
+ def initialize
56
+ @storage = {}
57
+ end
58
+
59
+ def list
60
+ @storage.keys
61
+ end
62
+
63
+ def load(key)
64
+ @storage[key]
65
+ end
66
+
67
+ def store(key, value)
68
+ @storage[key] = value
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,103 @@
1
+ require 'sinatra/base'
2
+ require 'haml'
3
+ require 'json'
4
+
5
+ module Nestene
6
+ module Ui
7
+ class App < Sinatra::Base
8
+
9
+
10
+ get '/autons.json' do
11
+ content_type :json
12
+ Celluloid::Actor[:nestene_core].auton_names.to_json
13
+ end
14
+
15
+ get '/credentials' do
16
+ content_type :json
17
+ Celluloid::Actor[:nestene_core].get_credentials.to_json
18
+ end
19
+
20
+ post '/credentials' do
21
+ content_type :json
22
+ begin
23
+ credentials = JSON.parse(request.body.read)
24
+ auton_id = Celluloid::Actor[:nestene_core].set_credentials credentials
25
+ status 200
26
+ rescue Exception => e
27
+ puts e
28
+ status 500
29
+ {message: e.message}.to_json
30
+ end
31
+ end
32
+
33
+ post '/autons' do
34
+ content_type :json
35
+ begin
36
+ type = JSON.parse(request.body.read).fetch('auton_type')
37
+ raise "auton_type not set" unless type
38
+ auton_id = Celluloid::Actor[:nestene_core].create_auton type
39
+ status 201
40
+ {auton_id: auton_id}.to_json
41
+ rescue Exception => e
42
+ status 500
43
+ {message: e.message}.to_json
44
+ end
45
+
46
+ end
47
+
48
+ get '/' do
49
+ @auton_names = Celluloid::Actor[:nestene_core].auton_names
50
+ haml :index
51
+ end
52
+
53
+ get '/auton/create' do
54
+ haml :auton_form
55
+ end
56
+
57
+ get '/auton/:id' do
58
+ @auton_id = params[:id]
59
+ @auton_state = Celluloid::Actor[:nestene_core].get_state(@auton_id)
60
+ haml :auton
61
+ end
62
+
63
+ get '/auton/:id/schedule_step' do
64
+ @auton_id = params[:id]
65
+ @auton_state = Celluloid::Actor[:nestene_core].get_state(@auton_id)
66
+ auton_type = @auton_state.type_as_class
67
+ @available_methods = auton_type.instance_methods(false).map{|name| auton_type.instance_method(name)}
68
+ @number_of_params = 0
69
+ haml :schedule_step
70
+ end
71
+
72
+ post '/auton/:id/schedule_step' do
73
+ parameters = params[:parameters] || []
74
+ step_name = params[:step_name]
75
+
76
+ @auton_id = params[:id]
77
+ @auton_state = Celluloid::Actor[:nestene_core].get_state(@auton_id)
78
+ auton_type = @auton_state.type_as_class
79
+ @available_methods = auton_type.instance_methods(false).sort.map{|name| auton_type.instance_method(name)}
80
+
81
+ method = @available_methods.find{|m| m.name.to_s == step_name}
82
+
83
+ if method && method.arity == parameters.size
84
+ step_id = Celluloid::Actor[:nestene_core].schedule_step(params[:id],step_name, parameters)
85
+ headers['X-AUTON-STEP-ID'] = step_id
86
+ redirect '/auton/%s' % params[:id]
87
+ else
88
+ @method = method
89
+ @number_of_params = method ? method.arity : 0
90
+ @step_name = step_name
91
+ haml :schedule_step
92
+ end
93
+ end
94
+
95
+ post '/auton/create' do
96
+ Celluloid::Actor[:nestene_core].create_auton params[:auton_type]
97
+ redirect '/'
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,75 @@
1
+ var nestene = angular.module('nestene', ['JSONedit']);
2
+
3
+ nestene.controller('AutonsController', function($scope, $http, $timeout) {
4
+ $scope.autons=[];
5
+
6
+ $scope.getAutons = function() {
7
+ var autonsPromise = $http.get("autons.json");
8
+ autonsPromise.success(function(data, status, headers, config) {
9
+ $scope.autons = data;
10
+ });
11
+
12
+ autonsPromise.error(function(data, status, headers, config) {
13
+ $.bootstrapGrowl("getting autons failed!", { type: 'danger' });
14
+ });
15
+ }
16
+
17
+ $scope.intervalFunction = function() {
18
+ $timeout(function() {
19
+ $scope.getAutons();
20
+ $scope.intervalFunction();
21
+ }, 1000);
22
+ };
23
+
24
+ $scope.intervalFunction();
25
+
26
+ });
27
+
28
+ nestene.controller('CreateAutonFormController', function($scope,$http) {
29
+ $scope.autonType = '';
30
+ $scope.createAuton = function() {
31
+ var postPromise = $http.post("autons",{auton_type: $scope.autonType});
32
+ postPromise.success(function(data, status, headers, config) {
33
+ $.bootstrapGrowl("Created "+data['auton_id'], { type: 'success' });
34
+ });
35
+ postPromise.error(function(data, status, headers, config) {
36
+ $.bootstrapGrowl("Could not create Auton: "+data['message'], { type: 'error' });
37
+ });
38
+ };
39
+ });
40
+
41
+ nestene.controller('CredentialsController', function($scope,$http) {
42
+ $scope.credentials={};
43
+ $scope.loaded = false;
44
+
45
+
46
+ $scope.loadCredentials = function() {
47
+ var getPromise = $http.get("credentials");
48
+ getPromise.success(function(data, status, headers, config) {
49
+ $scope.credentials = data;
50
+ $scope.loaded = true;
51
+ });
52
+
53
+ getPromise.error(function(data, status, headers, config) {
54
+ $.bootstrapGrowl("getting credentials failed!", { type: 'danger' });
55
+ });
56
+ }
57
+
58
+ $scope.loadCredentials();
59
+
60
+ $scope.storeCredentials = function() {
61
+ var postPromise = $http.post("credentials",$scope.credentials);
62
+ postPromise.success(function(data, status, headers, config) {
63
+ $.bootstrapGrowl("Credentials saved", { type: 'success' });
64
+ });
65
+ postPromise.error(function(data, status, headers, config) {
66
+ $.bootstrapGrowl("Could not save credentials", { type: 'danger' });
67
+ });
68
+ };
69
+ });
70
+
71
+
72
+
73
+
74
+
75
+
@@ -0,0 +1 @@
1
+ !function(a,b){"use strict";b.module("ui.sortable",[]).value("uiSortableConfig",{}).directive("uiSortable",["uiSortableConfig","$timeout","$log",function(a,c,d){return{require:"?ngModel",link:function(e,f,g,h){function i(a,b){return b&&"function"==typeof b?function(c,d){a(c,d),b(c,d)}:a}function j(a,b){var c=a.sortable("option","helper");return"clone"===c||"function"==typeof c&&b.item.sortable.isCustomHelperUsed()}var k,l={},m={receive:null,remove:null,start:null,stop:null,update:null},n={helper:null};return b.extend(l,a,e.$eval(g.uiSortable)),b.element.fn&&b.element.fn.jquery?(h?(e.$watch(g.ngModel+".length",function(){c(function(){f.data("ui-sortable")&&f.sortable("refresh")})}),m.start=function(a,b){b.item.sortable={index:b.item.index(),cancel:function(){b.item.sortable._isCanceled=!0},isCanceled:function(){return b.item.sortable._isCanceled},isCustomHelperUsed:function(){return!!b.item.sortable._isCustomHelperUsed},_isCanceled:!1,_isCustomHelperUsed:b.item.sortable._isCustomHelperUsed}},m.activate=function(){k=f.contents();var a=f.sortable("option","placeholder");if(a&&a.element&&"function"==typeof a.element){var c=a.element();c=b.element(c);var d=f.find('[class="'+c.attr("class")+'"]');k=k.not(d)}},m.update=function(a,b){b.item.sortable.received||(b.item.sortable.dropindex=b.item.index(),b.item.sortable.droptarget=b.item.parent(),f.sortable("cancel")),j(f,b)&&!b.item.sortable.received&&(k=k.not(k.last())),k.appendTo(f),b.item.sortable.received&&(k=null),b.item.sortable.received&&!b.item.sortable.isCanceled()&&e.$apply(function(){h.$modelValue.splice(b.item.sortable.dropindex,0,b.item.sortable.moved)})},m.stop=function(a,b){!b.item.sortable.received&&"dropindex"in b.item.sortable&&!b.item.sortable.isCanceled()?e.$apply(function(){h.$modelValue.splice(b.item.sortable.dropindex,0,h.$modelValue.splice(b.item.sortable.index,1)[0])}):"dropindex"in b.item.sortable&&!b.item.sortable.isCanceled()||j(f,b)||k.appendTo(f),k=null},m.receive=function(a,b){b.item.sortable.received=!0},m.remove=function(a,b){"dropindex"in b.item.sortable||(f.sortable("cancel"),b.item.sortable.cancel()),b.item.sortable.isCanceled()||e.$apply(function(){b.item.sortable.moved=h.$modelValue.splice(b.item.sortable.index,1)[0]})},n.helper=function(a){return a&&"function"==typeof a?function(b,c){var d=a(b,c);return c.sortable._isCustomHelperUsed=c!==d,d}:a},e.$watch(g.uiSortable,function(a){f.data("ui-sortable")&&b.forEach(a,function(a,b){m[b]?("stop"===b&&(a=i(a,function(){e.$apply()})),a=i(m[b],a)):n[b]&&(a=n[b](a)),f.sortable("option",b,a)})},!0),b.forEach(m,function(a,b){l[b]=i(a,l[b])})):d.info("ui.sortable: ngModel not provided!",f),void f.sortable(l)):void d.error("ui.sortable: jQuery should be included before AngularJS!")}}}])}(window,window.angular);