cloudinary 1.0.85 → 1.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.
- checksums.yaml +5 -13
- data/.gitignore +5 -5
- data/CHANGELOG.md +13 -0
- data/README.md +8 -5
- data/Rakefile +1 -1
- data/cloudinary.gemspec +1 -1
- data/lib/cloudinary/carrier_wave.rb +18 -4
- data/lib/cloudinary/carrier_wave/process.rb +1 -1
- data/lib/cloudinary/carrier_wave/storage.rb +11 -17
- data/lib/cloudinary/utils.rb +554 -551
- data/lib/cloudinary/version.rb +1 -1
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +149 -46
- metadata +21 -21
data/lib/cloudinary/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* Cloudinary's jQuery library - v1.0.
|
2
|
+
* Cloudinary's jQuery library - v1.0.24
|
3
3
|
* Copyright Cloudinary
|
4
4
|
* see https://github.com/cloudinary/cloudinary_js
|
5
5
|
*/
|
@@ -30,7 +30,8 @@
|
|
30
30
|
var OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net";
|
31
31
|
var AKAMAI_SHARED_CDN = "res.cloudinary.com";
|
32
32
|
var SHARED_CDN = AKAMAI_SHARED_CDN;
|
33
|
-
var DEFAULT_POSTER_OPTIONS = { format: 'jpg', resource_type: 'video' }
|
33
|
+
var DEFAULT_POSTER_OPTIONS = { format: 'jpg', resource_type: 'video' };
|
34
|
+
var DEFAULT_VIDEO_SOURCE_TYPES = ['webm', 'mp4', 'ogv'];
|
34
35
|
function utf8_encode (argString) {
|
35
36
|
// http://kevin.vanzonneveld.net
|
36
37
|
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
|
@@ -285,20 +286,30 @@
|
|
285
286
|
return base_transformations.join("/");
|
286
287
|
}
|
287
288
|
|
289
|
+
var number_pattern = "([0-9]*)\\.([0-9]+)|([0-9]+)";
|
290
|
+
var offset_any_pattern = "(" + number_pattern + ")([%pP])?";
|
291
|
+
|
288
292
|
function split_range(range) {
|
289
|
-
var
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
293
|
+
var splitted;
|
294
|
+
switch(range.constructor){
|
295
|
+
case String:
|
296
|
+
var offset_any_pattern_re = "("+offset_any_pattern+ ")\.\.("+offset_any_pattern+")";
|
297
|
+
if (range.match(offset_any_pattern_re )){
|
298
|
+
splitted = range.split("..");
|
299
|
+
}
|
300
|
+
break;
|
301
|
+
case Array:
|
302
|
+
splitted = range;
|
303
|
+
break;
|
304
|
+
default :
|
305
|
+
splitted = [null, null];
|
306
|
+
|
296
307
|
}
|
297
|
-
return
|
308
|
+
return splitted;
|
298
309
|
}
|
299
310
|
|
300
311
|
function norm_range_value(value) {
|
301
|
-
var offset = value.match(new RegExp("^" + offset_any_pattern
|
312
|
+
var offset = String(value).match(new RegExp("^" + offset_any_pattern+ "$"));
|
302
313
|
if( offset){
|
303
314
|
var modifier = present(offset[5]) ? 'p' : '';
|
304
315
|
value = (offset[1] || offset[4]) + modifier;
|
@@ -306,14 +317,6 @@
|
|
306
317
|
return value;
|
307
318
|
}
|
308
319
|
|
309
|
-
function number_pattern(){
|
310
|
-
return "([0-9]*)\\.([0-9]+)|([0-9]+)";
|
311
|
-
}
|
312
|
-
|
313
|
-
function offset_any_pattern(){
|
314
|
-
return "(" + number_pattern() + ")([%pP])?";
|
315
|
-
}
|
316
|
-
|
317
320
|
function absolutize(url) {
|
318
321
|
if (!url.match(/^https?:\//)) {
|
319
322
|
var prefix = document.location.protocol + "//" + document.location.host;
|
@@ -502,6 +505,23 @@
|
|
502
505
|
return null;
|
503
506
|
}
|
504
507
|
}
|
508
|
+
|
509
|
+
function join_pair(key, value) {
|
510
|
+
if (!value) {
|
511
|
+
return undefined;
|
512
|
+
} else if (value === true) {
|
513
|
+
return key;
|
514
|
+
} else {
|
515
|
+
return key + "=\"" + value + "\"";
|
516
|
+
}
|
517
|
+
}
|
518
|
+
|
519
|
+
function html_attrs(attrs) {
|
520
|
+
var pairs = $.map(attrs, function(value, key) { return join_pair(key, value); });
|
521
|
+
pairs.sort();
|
522
|
+
return pairs.filter(function(pair){return pair;}).join(" ");
|
523
|
+
}
|
524
|
+
|
505
525
|
var cloudinary_config = null;
|
506
526
|
var responsive_config = null;
|
507
527
|
var responsive_resize_initialized = false;
|
@@ -512,7 +532,6 @@
|
|
512
532
|
OLD_AKAMAI_SHARED_CDN: OLD_AKAMAI_SHARED_CDN,
|
513
533
|
AKAMAI_SHARED_CDN: AKAMAI_SHARED_CDN,
|
514
534
|
SHARED_CDN: SHARED_CDN,
|
515
|
-
DEFAULT_POSTER_OPTIONS: DEFAULT_POSTER_OPTIONS,
|
516
535
|
config: function(new_config, new_value) {
|
517
536
|
if (!cloudinary_config) {
|
518
537
|
cloudinary_config = {};
|
@@ -538,7 +557,7 @@
|
|
538
557
|
return cloudinary_url(public_id, options);
|
539
558
|
},
|
540
559
|
video_thumbnail_url: function(public_id, options) {
|
541
|
-
options = $.extend(DEFAULT_POSTER_OPTIONS, options);
|
560
|
+
options = $.extend({}, DEFAULT_POSTER_OPTIONS, options);
|
542
561
|
return cloudinary_url(public_id, options);
|
543
562
|
},
|
544
563
|
url_internal: cloudinary_url,
|
@@ -546,6 +565,13 @@
|
|
546
565
|
options = $.extend({}, options);
|
547
566
|
return generate_transformation_string(options);
|
548
567
|
},
|
568
|
+
/**
|
569
|
+
* Generate an HTML image tag based on the public ID and options
|
570
|
+
* as a jQuery object. The tag is preinitialized with cloudinary functionality.
|
571
|
+
* @param {String} public_id the cloudinary resource ID
|
572
|
+
* @param {String} [options]
|
573
|
+
* @returns {jQuery} Image tag
|
574
|
+
*/
|
549
575
|
image: function(public_id, options) {
|
550
576
|
options = $.extend({}, options);
|
551
577
|
var url = prepare_html_url(public_id, options);
|
@@ -553,7 +579,8 @@
|
|
553
579
|
return img;
|
554
580
|
},
|
555
581
|
video_thumbnail: function(public_id, options) {
|
556
|
-
|
582
|
+
options = $.extend({}, DEFAULT_POSTER_OPTIONS, options);
|
583
|
+
image(public_id, options);
|
557
584
|
},
|
558
585
|
facebook_profile_image: function(public_id, options) {
|
559
586
|
return $.cloudinary.image(public_id, $.extend({type: 'facebook'}, options));
|
@@ -570,6 +597,76 @@
|
|
570
597
|
fetch_image: function(public_id, options) {
|
571
598
|
return $.cloudinary.image(public_id, $.extend({type: 'fetch'}, options));
|
572
599
|
},
|
600
|
+
/**
|
601
|
+
* Creates an HTML video tag for the provided public_id
|
602
|
+
* @param {String} public_id the resource public ID
|
603
|
+
* @param {Object} [options] options for the resource and HTML tag
|
604
|
+
* @param {(String|Array<String>)} [options.source_types] Specify which
|
605
|
+
* source type the tag should include. defaults to webm, mp4 and ogv.
|
606
|
+
* @param {String} [options.source_transformation] specific transformations
|
607
|
+
* to use for a specific source type.
|
608
|
+
* @param {(String|Object)} [options.poster] image URL or
|
609
|
+
* poster options that may include a <tt>public_id</tt> key and
|
610
|
+
* poster-specific transformations
|
611
|
+
* @example <caption>Exmaple of generating a video tag:</caption>
|
612
|
+
* $.cloudinary.video("mymovie.mp4");
|
613
|
+
* $.cloudinary.video("mymovie.mp4", {source_types: 'webm'});
|
614
|
+
* $.cloudinary.video("mymovie.ogv", {poster: "myspecialplaceholder.jpg"});
|
615
|
+
* $.cloudinary.video("mymovie.webm", {source_types: ['webm', 'mp4'], poster: {effect: 'sepia'}});
|
616
|
+
* @return {string} HTML video tag
|
617
|
+
*/
|
618
|
+
video: function(public_id, options) {
|
619
|
+
options = options || {};
|
620
|
+
public_id = public_id.replace(/\.(mp4|ogv|webm)$/, '');
|
621
|
+
var source_types = option_consume(options, 'source_types', []);
|
622
|
+
var source_transformation = option_consume(options, 'source_transformation', {});
|
623
|
+
var fallback = option_consume(options, 'fallback_content', '');
|
624
|
+
|
625
|
+
if (source_types.length == 0) source_types = DEFAULT_VIDEO_SOURCE_TYPES;
|
626
|
+
var video_options = $.extend(true, {}, options);
|
627
|
+
|
628
|
+
if (video_options.hasOwnProperty('poster')) {
|
629
|
+
if ($.isPlainObject(video_options.poster)) {
|
630
|
+
if (video_options.poster.hasOwnProperty('public_id')) {
|
631
|
+
video_options.poster = cloudinary_url(video_options.poster.public_id, video_options.poster);
|
632
|
+
} else {
|
633
|
+
video_options.poster = cloudinary_url(public_id, $.extend({}, DEFAULT_POSTER_OPTIONS, video_options.poster));
|
634
|
+
}
|
635
|
+
}
|
636
|
+
} else {
|
637
|
+
video_options.poster = cloudinary_url(public_id, $.extend({}, DEFAULT_POSTER_OPTIONS, options));
|
638
|
+
}
|
639
|
+
|
640
|
+
if (!video_options.poster) delete video_options.poster;
|
641
|
+
|
642
|
+
var html = '<video ';
|
643
|
+
|
644
|
+
if (!video_options.hasOwnProperty('resource_type')) video_options.resource_type = 'video';
|
645
|
+
var multi_source = $.isArray(source_types) && source_types.length > 1;
|
646
|
+
var source = public_id;
|
647
|
+
if (!multi_source){
|
648
|
+
source = source + '.' + build_array(source_types)[0];
|
649
|
+
}
|
650
|
+
var src = cloudinary_url(source, video_options);
|
651
|
+
if (!multi_source) video_options.src = src;
|
652
|
+
if (video_options.hasOwnProperty("html_width")) video_options.width = option_consume(video_options, 'html_width');
|
653
|
+
if (video_options.hasOwnProperty("html_height")) video_options.height = option_consume(video_options, 'html_height');
|
654
|
+
html = html + html_attrs(video_options) + '>';
|
655
|
+
if (multi_source) {
|
656
|
+
for(var i = 0; i < source_types.length; i++) {
|
657
|
+
var source_type = source_types[i];
|
658
|
+
var transformation = source_transformation[source_type] || {};
|
659
|
+
src = cloudinary_url(source + "." + source_type, $.extend(true, {resource_type: 'video'}, options, transformation));
|
660
|
+
var video_type = source_type == 'ogv' ? 'ogg' : source_type;
|
661
|
+
var mime_type = "video/" + video_type;
|
662
|
+
html = html + '<source '+ html_attrs({src: src, type: mime_type}) + '>';
|
663
|
+
}
|
664
|
+
}
|
665
|
+
|
666
|
+
html = html + fallback;
|
667
|
+
html = html + '</video>';
|
668
|
+
return html;
|
669
|
+
},
|
573
670
|
sprite_css: function(public_id, options) {
|
574
671
|
options = $.extend({type: 'sprite'}, options);
|
575
672
|
if (!public_id.match(/.css$/)) options.format = 'css';
|
@@ -580,15 +677,16 @@
|
|
580
677
|
* Use the following classes:
|
581
678
|
* - cld-hidpi - only set dpr_auto
|
582
679
|
* - cld-responsive - update both dpr_auto and w_auto
|
583
|
-
* @param: options
|
584
|
-
* - responsive_resize - should responsive images be updated on resize (default: true).
|
585
|
-
* - responsive_debounce - if set, how many milliseconds after resize is done before the image is replaces (default: 100). Set to 0 to disable.
|
586
|
-
* - responsive_use_stoppoints:
|
587
|
-
* - true - always use stoppoints for width
|
588
|
-
* - "resize" - use exact width on first render and stoppoints on resize (default)
|
589
|
-
* - false - always use exact width
|
590
680
|
* Stoppoints - to prevent creating a transformation for every pixel, stop-points can be configured. The smallest stop-point that is larger than
|
591
681
|
* the wanted width will be used. The default stoppoints are all the multiples of 10. See calc_stoppoint for ways to override this.
|
682
|
+
* @param {Object} options
|
683
|
+
* @param {boolean} [options.responsive_resize=true] should responsive images be updated on resize (default: true).
|
684
|
+
* @param {integer} [options.responsive_debounce=100] if set, how many milliseconds after resize is done before the image is replaces (default: 100). Set to 0 to disable.
|
685
|
+
* @param {boolean|String} [options.responsive_use_stoppoints]
|
686
|
+
* <ul><li>true - always use stoppoints for width</li>
|
687
|
+
* <li>"resize" - use exact width on first render and stoppoints on resize (default)</li>
|
688
|
+
* <li>false - always use exact width</li></ul>
|
689
|
+
* @param {boolean} [options.responsive_preserve_height] if set to true, original css height is perserved. Should only be used if the transformation supports different aspect ratios.
|
592
690
|
*/
|
593
691
|
responsive: function(options) {
|
594
692
|
responsive_config = $.extend(responsive_config || {}, options);
|
@@ -667,15 +765,15 @@
|
|
667
765
|
|
668
766
|
/**
|
669
767
|
* Update hidpi (dpr_auto) and responsive (w_auto) fields according to the current container size and the device pixel ratio.
|
670
|
-
* Only images marked with the cld-responsive class have w_auto updated.
|
671
|
-
* options
|
672
|
-
*
|
673
|
-
*
|
674
|
-
*
|
675
|
-
*
|
676
|
-
*
|
677
|
-
*
|
678
|
-
*
|
768
|
+
* Only images marked with the <tt>cld-responsive</tt> class have w_auto updated.
|
769
|
+
* @param {Object} [options] Options
|
770
|
+
* @param {boolean|String} [options.responsive_use_stoppoints]
|
771
|
+
* <ul><li>true - always use stoppoints for width</li>
|
772
|
+
* <li>"resize" - use exact width on first render and stoppoints on resize (default)</li>
|
773
|
+
* <li>false - always use exact width</li></ul>
|
774
|
+
* @param {boolean} [options.responsive] if true, enable responsice on this element. Note that $.cloudinary.responsive() should be called once on the page.
|
775
|
+
* @param {boolean} [options.responsive_preserve_height] if set to true, original css height is perserved. Should only be used if the transformation supports different aspect ratios.
|
776
|
+
* @return {$.fn}
|
679
777
|
*/
|
680
778
|
$.fn.cloudinary_update = function(options) {
|
681
779
|
options = options || {};
|
@@ -700,8 +798,8 @@
|
|
700
798
|
|
701
799
|
for (nthParent = 0; nthParent < parentsLength; nthParent+=1) {
|
702
800
|
container = parents[nthParent];
|
703
|
-
|
704
|
-
|
801
|
+
containerWidth = $(container).width();
|
802
|
+
if (containerWidth) {
|
705
803
|
break;
|
706
804
|
}
|
707
805
|
}
|
@@ -721,7 +819,7 @@
|
|
721
819
|
}
|
722
820
|
src = src.replace(/\bw_auto\b/g, "w_" + requestedWidth);
|
723
821
|
attrs.width = null;
|
724
|
-
attrs.height = null;
|
822
|
+
if (!options.responsive_preserve_height) attrs.height = null;
|
725
823
|
}
|
726
824
|
// Update dpr according to the device's devicePixelRatio
|
727
825
|
attrs.src = src.replace(/\bdpr_(1\.0|auto)\b/g, "dpr_" + $.cloudinary.device_pixel_ratio());
|
@@ -730,7 +828,6 @@
|
|
730
828
|
return this;
|
731
829
|
};
|
732
830
|
|
733
|
-
|
734
831
|
var webp = null;
|
735
832
|
$.fn.webpify = function(options, webp_options) {
|
736
833
|
var that = this;
|
@@ -838,7 +935,9 @@
|
|
838
935
|
|
839
936
|
if (!this.fileupload('option').url) {
|
840
937
|
var cloud_name = options.cloud_name || $.cloudinary.config().cloud_name;
|
841
|
-
var
|
938
|
+
var resource_type = options.resource_type || "auto";
|
939
|
+
var type = options.type || "upload";
|
940
|
+
var upload_url = "https://api.cloudinary.com/v1_1/" + cloud_name + "/" + resource_type + "/" + type;
|
842
941
|
this.fileupload('option', 'url', upload_url);
|
843
942
|
}
|
844
943
|
}
|
@@ -855,9 +954,13 @@
|
|
855
954
|
options = options || {};
|
856
955
|
upload_params = $.extend({}, upload_params) || {};
|
857
956
|
|
858
|
-
|
859
|
-
|
860
|
-
|
957
|
+
var attrs_to_move = ["cloud_name", "resource_type", "type"];
|
958
|
+
for (var i = 0; i < attrs_to_move.length; i++) {
|
959
|
+
var attr = attrs_to_move[i];
|
960
|
+
if (upload_params[attr]) {
|
961
|
+
options[attr] = upload_params[attr];
|
962
|
+
delete upload_params[attr];
|
963
|
+
}
|
861
964
|
}
|
862
965
|
|
863
966
|
// Serialize upload_params
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudinary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nadav Soferman
|
@@ -10,90 +10,90 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-04-
|
13
|
+
date: 2015-04-21 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: aws_cf_signer
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
|
-
- -
|
26
|
+
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: '0'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: rspec
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
|
-
- -
|
33
|
+
- - ">="
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '2
|
35
|
+
version: '3.2'
|
36
36
|
type: :development
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
|
-
- -
|
40
|
+
- - ">="
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '2
|
42
|
+
version: '3.2'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: rspec-rails
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- -
|
47
|
+
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: '0'
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
|
-
- -
|
54
|
+
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: '0'
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: rest-client
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
|
-
- -
|
61
|
+
- - ">="
|
62
62
|
- !ruby/object:Gem::Version
|
63
63
|
version: '0'
|
64
64
|
type: :runtime
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
|
-
- -
|
68
|
+
- - ">="
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '0'
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
72
|
name: actionpack
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
|
-
- -
|
75
|
+
- - ">="
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
|
-
- -
|
82
|
+
- - ">="
|
83
83
|
- !ruby/object:Gem::Version
|
84
84
|
version: '0'
|
85
85
|
- !ruby/object:Gem::Dependency
|
86
86
|
name: simplecov
|
87
87
|
requirement: !ruby/object:Gem::Requirement
|
88
88
|
requirements:
|
89
|
-
- -
|
89
|
+
- - ">="
|
90
90
|
- !ruby/object:Gem::Version
|
91
91
|
version: '0'
|
92
92
|
type: :development
|
93
93
|
prerelease: false
|
94
94
|
version_requirements: !ruby/object:Gem::Requirement
|
95
95
|
requirements:
|
96
|
-
- -
|
96
|
+
- - ">="
|
97
97
|
- !ruby/object:Gem::Version
|
98
98
|
version: '0'
|
99
99
|
description: Client library for easily using the Cloudinary service
|
@@ -105,8 +105,8 @@ executables: []
|
|
105
105
|
extensions: []
|
106
106
|
extra_rdoc_files: []
|
107
107
|
files:
|
108
|
-
- .gitignore
|
109
|
-
- .rspec
|
108
|
+
- ".gitignore"
|
109
|
+
- ".rspec"
|
110
110
|
- CHANGELOG.md
|
111
111
|
- Gemfile
|
112
112
|
- README.md
|
@@ -169,17 +169,17 @@ require_paths:
|
|
169
169
|
- lib
|
170
170
|
required_ruby_version: !ruby/object:Gem::Requirement
|
171
171
|
requirements:
|
172
|
-
- -
|
172
|
+
- - ">="
|
173
173
|
- !ruby/object:Gem::Version
|
174
174
|
version: '0'
|
175
175
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
176
|
requirements:
|
177
|
-
- -
|
177
|
+
- - ">="
|
178
178
|
- !ruby/object:Gem::Version
|
179
179
|
version: '0'
|
180
180
|
requirements: []
|
181
181
|
rubyforge_project: cloudinary
|
182
|
-
rubygems_version: 2.
|
182
|
+
rubygems_version: 2.2.2
|
183
183
|
signing_key:
|
184
184
|
specification_version: 4
|
185
185
|
summary: Client library for easily using the Cloudinary service
|