text: ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) {
    var upload = UploadExif;
    upload.getAttrWithDefaults = function (attr, name) {
      if (attr[name] != null) return attr[name];
      var def = upload.defaults[name];
      return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def)));
    };
  
    upload.attrGetter = function (name, attr, scope, params) {
      var attrVal = this.getAttrWithDefaults(attr, name);
      if (scope) {
        try {
          if (params) {
            return $parse(attrVal)(scope, params);
          } else {
            return $parse(attrVal)(scope);
          }
        } catch (e) {
          // hangle string value without single qoute
          if (name.search(/min|max|pattern/i)) {
            return attrVal;
          } else {
            throw e;
          }
        }
      } else {
        return attrVal;
      }
    };
  
    upload.shouldUpdateOn = function (type, attr, scope) {
      var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope);
      if (modelOptions && modelOptions.updateOn) {
        return modelOptions.updateOn.split(' ').indexOf(type) > -1;
      }
      return true;
    };
  
    upload.emptyPromise = function () {
      var d = $q.defer();
      var args = arguments;
      $timeout(function () {
        d.resolve.apply(d, args);
      });
      return d.promise;
    };
  
    upload.rejectPromise = function () {
      var d = $q.defer();
      var args = arguments;
      $timeout(function () {
        d.reject.apply(d, args);
      });
      return d.promise;
    };
  
    upload.happyPromise = function (promise, data) {
      var d = $q.defer();
      promise.then(function (result) {
        d.resolve(result);
      }, function (error) {
        $timeout(function () {
          throw error;
        });
        d.resolve(data);
      });
      return d.promise;
    };
  
    function applyExifRotations(files, attr, scope) {
      var promises = [upload.emptyPromise()];
      angular.forEach(files, function (f, i) {
        if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) {
          promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) {
            files.splice(i, 1, fixedFile);
          }));
        }
      });
      return $q.all(promises);
    }
  
    function resizeFile(files, attr, scope, ngModel) {
      var resizeVal = upload.attrGetter('ngfResize', attr, scope);
      if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise();
      if (resizeVal instanceof Function) {
        var defer = $q.defer();
        return resizeVal(files).then(function (p) {
          resizeWithParams(p, files, attr, scope, ngModel).then(function (r) {
            defer.resolve(r);
          }, function (e) {
            defer.reject(e);
          });
        }, function (e) {
          defer.reject(e);
        });
      } else {
        return resizeWithParams(resizeVal, files, attr, scope, ngModel);
      }
    }
  
    function resizeWithParams(params, files, attr, scope, ngModel) {
      var promises = [upload.emptyPromise()];
  
      function handleFile(f, i) {
        if (f.type.indexOf('image') === 0) {
          if (params.pattern && !upload.validatePattern(f, params.pattern)) return;
          params.resizeIf = function (width, height) {
            return upload.attrGetter('ngfResizeIf', attr, scope,
              {$width: width, $height: height, $file: f});
          };
          var promise = upload.resize(f, params);
          promises.push(promise);
          promise.then(function (resizedFile) {
            files.splice(i, 1, resizedFile);
          }, function (e) {
            f.$error = 'resize';
            (f.$errorMessages = (f.$errorMessages || {})).resize = true;
            f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name);
            ngModel.$ngfValidations.push({name: 'resize', valid: false});
            upload.applyModelValidation(ngModel, files);
          });
        }
      }
  
      for (var i = 0; i < files.length; i++) {
        handleFile(files[i], i);
      }
      return $q.all(promises);
    }
  
    upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) {
      function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) {
        attr.$$ngfPrevValidFiles = files;
        attr.$$ngfPrevInvalidFiles = invalidFiles;
        var file = files && files.length ? files[0] : null;
        var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null;
  
        if (ngModel) {
          upload.applyModelValidation(ngModel, files);
          ngModel.$setViewValue(isSingleModel ? file : files);
        }
  
        if (fileChange) {
          $parse(fileChange)(scope, {
            $files: files,
            $file: file,
            $newFiles: newFiles,
            $duplicateFiles: dupFiles,
            $invalidFiles: invalidFiles,
            $invalidFile: invalidFile,
            $event: evt
          });
        }
  
        var invalidModel = upload.attrGetter('ngfModelInvalid', attr);
        if (invalidModel) {
          $timeout(function () {
            $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles);
          });
        }
        $timeout(function () {
          // scope apply changes
        });
      }
  
      var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles,
        invalids = [], valids = [];
  
      function removeDuplicates() {
        function equals(f1, f2) {
          return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) &&
            f1.type === f2.type;
        }
  
        function isInPrevFiles(f) {
          var j;
          for (j = 0; j < prevValidFiles.length; j++) {
            if (equals(f, prevValidFiles[j])) {
              return true;
            }
          }
          for (j = 0; j < prevInvalidFiles.length; j++) {
            if (equals(f, prevInvalidFiles[j])) {
              return true;
            }
          }
          return false;
        }
  
        if (files) {
          allNewFiles = [];
          dupFiles = [];
          for (var i = 0; i < files.length; i++) {
            if (isInPrevFiles(files[i])) {
              dupFiles.push(files[i]);
            } else {
              allNewFiles.push(files[i]);
            }
          }
        }
      }
  
      function toArray(v) {
        return angular.isArray(v) ? v : [v];
      }
  
      function resizeAndUpdate() {
        function updateModel() {
          $timeout(function () {
            update(keep ? prevValidFiles.concat(valids) : valids,
              keep ? prevInvalidFiles.concat(invalids) : invalids,
              files, dupFiles, isSingleModel);
          }, options && options.debounce ? options.debounce.change || options.debounce : 0);
        }
  
        var resizingFiles = validateAfterResize ? allNewFiles : valids;
        resizeFile(resizingFiles, attr, scope, ngModel).then(function () {
          if (validateAfterResize) {
            upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)
              .then(function (validationResult) {
                valids = validationResult.validsFiles;
                invalids = validationResult.invalidsFiles;
                updateModel();
              });
          } else {
            updateModel();
          }
        }, function () {
          for (var i = 0; i < resizingFiles.length; i++) {
            var f = resizingFiles[i];
            if (f.$error === 'resize') {
              var index = valids.indexOf(f);
              if (index > -1) {
                valids.splice(index, 1);
                invalids.push(f);
              }
              updateModel();
            }
          }
        });
      }
  
      prevValidFiles = attr.$$ngfPrevValidFiles || [];
      prevInvalidFiles = attr.$$ngfPrevInvalidFiles || [];
      if (ngModel && ngModel.$modelValue) {
        prevValidFiles = toArray(ngModel.$modelValue);
      }
  
      var keep = upload.attrGetter('ngfKeep', attr, scope);
      allNewFiles = (files || []).slice(0);
      if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) {
        removeDuplicates(attr, scope);
      }
  
      var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr);
  
      if (keep && !allNewFiles.length) return;
  
      upload.attrGetter('ngfBeforeModelChange', attr, scope, {
        $files: files,
        $file: files && files.length ? files[0] : null,
        $newFiles: allNewFiles,
        $duplicateFiles: dupFiles,
        $event: evt
      });
  
      var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope);
  
      var options = upload.attrGetter('ngfModelOptions', attr, scope);
      upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope)
        .then(function (validationResult) {
        if (noDelay) {
          update(allNewFiles, [], files, dupFiles, isSingleModel);
        } else {
          if ((!options || !options.allowInvalid) && !validateAfterResize) {
            valids = validationResult.validFiles;
            invalids = validationResult.invalidFiles;
          } else {
            valids = allNewFiles;
          }
          if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) {
            applyExifRotations(valids, attr, scope).then(function () {
              resizeAndUpdate();
            });
          } else {
            resizeAndUpdate();
          }
        }
      });
    };
  
    return upload;
  }]);
  