Created
September 9, 2015 01:49
-
-
Save robcmills/8aa148dec6128cd8ccd2 to your computer and use it in GitHub Desktop.
selected code from codepath following bonus cam media submission to retry upload
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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