nestene 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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);