Skip to content

Instantly share code, notes, and snippets.

@robcmills
Created September 9, 2015 01:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robcmills/8aa148dec6128cd8ccd2 to your computer and use it in GitHub Desktop.
Save robcmills/8aa148dec6128cd8ccd2 to your computer and use it in GitHub Desktop.
selected code from codepath following bonus cam media submission to retry upload
// file input element gets media
// submission_media.hbs line:36
<form enctype="multipart/form-data" class='media-upload-form'>
// submission_media.js line:134
fileDidChange: function() {
//...
this.setFile(file);
setFile: function(file) {
// ...
this.setProperties({
file: file,
isError: false,
submission: {file: file.type}
});
this._mediaAdded = true;
this.performValidation();
return true;
// Play.SubmissionCameraWidget = Play.SubmissionMediaWidget.extend({
// Play.SubmissionMediaWidget = Play.SubmissionBaseWidget.extend({
// submission_base.js
Play.SubmissionBaseWidget = Ember.View.extend({
// ...
performValidation: function() {
var isValid = this.validate(this.get('submission') || {});
var cleaned = isValid ? this.clean(this.get('submission')) : null;
if(!this.get('controller')) { return; }
// controller = PlaySubmissionPart
this.get('controller').setProperties({
isValid: isValid,
validSubmission: isValid ? cleaned : null
});
},
// play_submission.js
Play.PlaySubmissionController = Ember.Controller.extend({
// ...
isValid: function() {
var parts = this.get('controllers.playSubmissionParts');
return parts.everyProperty('isValid');
}.property('controllers.playSubmissionParts.@each.isValid'),
isValidDidChange: function() {
if(this.get('isValid')) {
this.get('controllers.playWaypoint').checkAutosubmit();
}
}.observes('isValid'),
// play_behavior.js
var endlessCreative = Ember.Mixin.create(creative, {
autosubmit: true,
messages: {
ok: {message: null, completed: false, sync: false, upload: true}
}
});
Play.EndlessPhotoBehavior = Play.Behavior.extend(endlessCreative, {});
Play.EndlessMovieBehavior = Play.Behavior.extend(endlessCreative, {});
Play.EndlessCameraBehavior = Play.Behavior.extend(endlessCreative, {});
Play.Behavior = Ember.Object.extend({
getResponse: function(submission, attemptNum) {
var responseType = this.interpret(submission, attemptNum);
Ember.assert("Must have a message for response type " +
responseType, !!this.messages[responseType]);
var responseParams = this.messages[responseType];
var isCompleted = !!responseParams.completed;
var shouldSync = isCompleted || (responseParams.sync === true);
var shouldUpload = shouldSync || (responseParams.upload === true);
var message = responseParams.message ? {
message: responseParams.message,
style: responseParams.style,
correct: this.getCorrectText(),
given: this.getSubmissionText(submission)
} : null;
return {
isCompleted: isCompleted,
shouldSync: shouldSync,
shouldUpload: shouldUpload,
message: message
};
},
// play_waypoint.js
shouldAutosubmit: function() {
return !!this.get('behavior.autosubmit');
},
isValid: Ember.computed.alias('controllers.playSubmission.isValid'),
_isSubmitDisabled: function() {
if(this.get('isSubmitting')) { return true; }
if(!this.get('isValid')) { return true; }
return false;
},
checkAutosubmit: function() {
if(!this.shouldAutosubmit()) { return; }
if(this.isAutosubmitting) { return; }
if(this._isSubmitDisabled()) { return; }
Ember.run.later(this, '_checkAutosubmit', 1);
},
_checkAutosubmit: function() {
if(!this.shouldAutosubmit()) { return; }
if(this.isAutosubmitting) { return; }
if(this._isSubmitDisabled()) { return; }
this.autosubmit();
},
autosubmit: function() {
if(this.isAutosubmitting) { return; }
this.isAutosubmitting = true;
var submitPromise = this.submit();
if(this.get('isSubmitting')) {
submitPromise.then(B.bind(function() {
this.isAutosubmitting = false;
}, this));
} else {
this.isAutosubmitting = false;
}
},
pendingSubmissions: Ember.computed.alias(
'controllers.playWaypointProgress.pendingSubmissions'),
submit: function() {
// ... much omitted
// suspects:
var response = behavior.getResponse(submission, attemptNum);
var pendingSubmissions = Ember.copy(this.get('pendingSubmissions'));
if(response.shouldUpload) {
var maxUploadSize = Play.get('maxUploadSize');
files.forEach(function(file, fileIndex) {
var mediaItem = mediaItems[fileIndex];
var upload = Play.Upload.create({mediaItem: mediaItem, file: file});
if(file.dummy) {
// don't upload dummy files.
} else if(shouldUploadFiles && file.size < maxUploadSize) {
// if you're uploading files, add and start right away.
this.get('controllers.playSyncUploads').addAndStart(
upload, waypoint);
} else if (response.shouldSync) {
// if we're not uploading files
} else {
// if uploads are not starting, then if we're syncing, do nothing,
// since the files will be included in the sync. But if we are NOT
// syncing (i.e. for bonus cam), then send the 'pending' status
// update so that the images can be recovered in wired transfer.
var uploadController = (this.get('controllers.playSyncUploads')
.createUploadController(upload, waypoint));
uploadController.updateStatus('pending')
.then(null, function(err) {});
}
}, this);
}
// play_sync_uploads.js
Play.PlaySyncUploadsController = Ember.ArrayController.extend({
addAndStart: function(upload, waypoint) {
console.log('addAndStart', upload, waypoint);
var uploadController = this.add(upload, waypoint);
if(this.get('allowNewUploadStart')) {
console.log('allowNewUploadStart');
this.start(uploadController);
} else {
// send update that the media item is pending, just so the system
// knows that it's ready. otherwise it won't get a notification until
// it's started.
uploadController.updateStatus('pending');
}
return uploadController;
},
start: function(uploadController) {
console.log('start', uploadController);
uploadController.updateStatus('started');
uploadController.start().then(null, function() {
// ignore failure.
});
},
Play.PlaySyncUpload = Ember.Object.extend({
// ...
updateStatus: function(status) {
var options = {media_item: this.get('content.mediaItem'), status: status};
return this.get('waypoint').triggerAction('updateMedia', options);
},
_start: function() {
return this.get('content').start(); // Play.Upload
},
start: function() {
console.log('PlaySyncUpload start');
this.set('hasBeenStarted', true);
var retryable = Breadcrumb.retry(this._start, this, Play.xhrRetryOptions);
this.set('uploadRetryable', retryable);
var self = this;
return retryable.then(function() { self.confirmUploadOnCompletion(); });
},
// waypoint.py
@mutex
@action
def update_media(self, media_item, status='uploaded'):
media_index = first(
i for (i, m) in enumerate(self.media)
if m['upload_key'] == media_item['upload_key'])
# If this is new media, add it!
if media_index is None:
media_index = len(self.media)
self.media.append(dict(
media_item, media_index=media_index,
is_uploading=False, is_uploaded=False, is_ready=False))
# Update media!
STATUSES = {
'pending': {'is_uploading': False, 'is_uploaded': False},
'started': {'is_uploading': True, 'is_uploaded': False},
'uploaded': {'is_uploading': False, 'is_uploaded': True}
}
self.media[media_index].update(STATUSES.get(status, {}))
updates = {'media': self.media}
if not self.has_media:
updates['has_media'] = True
if not self.in_gallery and media_item.get('in_gallery'):
updates['in_gallery'] = True
self.update(**updates)
self.trigger('media-updated')
if status == 'uploaded':
self._reprocess(media_index)
return self
// upload.js
Play.Upload = Ember.Object.extend({
start: function() {
console.log('Play.Upload start');
if(this.get('isUploading')) { return; }
return this._start();
},
_start: function() {
this.deferred = Ember.RSVP.defer();
var self = this;
this.loadMediaPolicy().then(function() {
// media policy succeeded!
self.startUpload();
}, function() {
// media policy load failed
self.deferred.reject();
});
return this.deferred.promise;
},
startUpload: function() {
console.log('upload startUpload');
if(this.xhr !== null) { return; }
var self = this;
var xhr = Breadcrumb.NativeHooks.upload(
this.getPostUrl(), this.getUploadParams(), this.file,
function didComplete(e) {
if(self.xhr !== xhr) { return; } // skip if upload aborted.
self.didComplete(e); },
function didFail(e) {
if(self.xhr !== xhr) { return; } // skip if upload aborted.
self.didFail(e); },
function didProgress(e) {
if(self.xhr !== xhr) { return; } // skip if upload aborted.
self.didProgress(e); });
this.xhr = xhr;
this.startProgressWatch();
this.setProperties({isUploading: true, progress: 0, isReady: false});
},
// retry.js
Breadcrumb.retry = function(options, action, context) {
console.log('Breadcrumb.retry', options, action, context);
var opts = options;
if(typeof options === 'function') { // action, context, [options]
opts = arguments[2] || {};
opts.action = arguments[0];
opts.context = arguments[1];
} // otherwise, called with {options}, no changes necessary
return Breadcrumb.Retryable.create(opts).start();
};
Breadcrumb.Retryable = Ember.Object.extend({
start: function() {
console.log('Retryable start');
if(this.get('isCompleted') || this.get('isInProgress')) {
throw new Error("Cannot start a Retryable more than once."); }
if(!this.deferred || this.deferred.promise.isRejected) {
this.deferred = Ember.RSVP.defer(); }
this.setProperties({
isInProgress: true,
isActing: true,
isError: false,
isFailure: false,
numAttempts: 1
});
this.act();
return this;
},
act: function() {
var result = this.action.call(this.context), deferred = this.deferred;
if(!result || !result.then) { // we are not a promise; resolve with value
if(this.deferred !== deferred) { return; } // had been restarted
this.onResolve(result);
} else { // returned a promise, so execute.
result.then(
B.bind(function(value) {
var t1 = new Date().getTime();
if(this.deferred !== deferred) { return; } // had been restarted
this.onResolve(value);
}, this),
B.bind(function(err) {
if(this.deferred !== deferred) { return; } // had been restarted
this.onReject(err);
}, this));
}
},
onReject: function() {
console.log('retry onReject');
// Are we final rejection?
if(this.get('numAttempts') >= this.get('maxAttempts')) {
this.setProperties({
isInProgress: false,
isActing: false,
isRetrying: false,
isWaitingToRetry: false,
isError: true,
isFailure: true
});
this.deferred.reject(new Error({
message: "Attempt " + this.get('numAttempts') +
" failed."}));
this.deferred = null;
return;
}
// If not, retry.
var numAttempts = this.get('numAttempts'),
retryBackoff = this.retryBackoffForAttemptNum(numAttempts);
this.setProperties({
numAttempts: numAttempts + 1,
retryAt: new Date(new Date().getTime() + retryBackoff),
isError: true,
isRetrying: true,
isWaitingToRetry: true,
isActing: false
});
// * PRIMARY SUSPECT!
this.retryIntervalId = setTimeout(
B.bind(this.retry, this), retryBackoff);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment