diff --git a/dss/settings.py b/dss/settings.py index ef00cb6..8c69a3a 100755 --- a/dss/settings.py +++ b/dss/settings.py @@ -1,19 +1,18 @@ #e Django settings for dss project. -from datetime import timedelta import os - +import mimetypes from django.core.urlresolvers import reverse_lazy import djcelery from django.conf import global_settings -import sys -from dss import localsettings from dss import logsettings from utils import here +from localsettings import * +from pipelinesettings import * +from paymentsettings import * -DEBUG = localsettings.DEBUG -DEVELOPMENT = localsettings.DEBUG +DEVELOPMENT = DEBUG TEMPLATE_DEBUG = DEBUG @@ -24,23 +23,15 @@ ADMINS = ( MANAGERS = ADMINS AUTH_PROFILE_MODULE = 'spa.UserProfile' -ALLOWED_HOSTS = ['*'] #localsettings.ALLOWED_HOSTS if hasattr(localsettings, 'ALLOWED_HOSTS') else [] +ALLOWED_HOSTS = ['*'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'deepsouthsounds', 'ADMINUSER': 'postgres', - 'USER': localsettings.DATABASE_USER if hasattr(localsettings, 'DATABASE_USER') else 'deepsouthsounds', - 'PASSWORD': localsettings.DATABASE_PASSWORD if hasattr(localsettings, 'DATABASE_PASSWORD') else '', - 'HOST': localsettings.DATABASE_HOST if hasattr(localsettings, 'DATABASE_HOST') else 'localhost', - }, - 'test': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'test_deepsouthsounds', - 'ADMINUSER': 'postgres', - 'USER': localsettings.DATABASE_USER if hasattr(localsettings, 'DATABASE_USER') else 'deepsouthsounds', - 'PASSWORD': localsettings.DATABASE_PASSWORD if hasattr(localsettings, 'DATABASE_PASSWORD') else '', - 'HOST': localsettings.DATABASE_HOST if hasattr(localsettings, 'DATABASE_HOST') else 'localhost', + 'USER': DATABASE_USER, + 'PASSWORD': DATABASE_PASSWORD, + 'HOST': DATABASE_HOST, } } import sys @@ -57,9 +48,7 @@ USE_L10N = True s = True SITE_ROOT = here('') -MEDIA_ROOT = localsettings.MEDIA_ROOT -STATIC_ROOT = localsettings.STATIC_ROOT -CACHE_ROOT = localsettings.CACHE_ROOT + CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', @@ -67,13 +56,6 @@ CACHES = { } } -STATIC_URL = localsettings.STATIC_URL if hasattr(localsettings, 'STATIC_URL') else '/static/' - -if DEBUG: - MEDIA_URL = localsettings.MEDIA_URL if hasattr(localsettings, 'MEDIA_URL') else '/media/' -else: - MEDIA_URL = localsettings.MEDIA_URL if hasattr(localsettings, 'MEDIA_URL') else '/static/' - ADMIN_MEDIA_PREFIX = STATIC_URL + "grappelli/" TINYMCE_JS_URL = os.path.join(STATIC_ROOT, "js/libs/tiny_mce/tiny_mce.js") @@ -116,8 +98,6 @@ STATICFILES_DIRS = ( here('static'), ) -SECRET_KEY = localsettings.SECRET_KEY - TEMPLATE_LOADERS = ( ('django.template.loaders.cached.Loader', ( 'django.template.loaders.filesystem.Loader', @@ -158,6 +138,7 @@ MIDDLEWARE_CLASSES = ( WSGI_APPLICATION = 'dss.wsgi.application' TEMPLATE_DIRS = (here('templates'),) + INSTALLED_APPS = ( 'grappelli', 'django.contrib.admin', @@ -204,13 +185,6 @@ LOGOUT_URL = reverse_lazy('home') LOGGING = logsettings.LOGGING FACEBOOK_APP_ID = '154504534677009' -FACEBOOK_APP_SECRET = localsettings.FACEBOOK_APP_SECRET - -BROKER_HOST = localsettings.BROKER_HOST -BROKER_PORT = localsettings.BROKER_PORT -BROKER_VHOST = localsettings.BROKER_VHOST -BROKER_USER = localsettings.BROKER_USER -BROKER_PASSWORD = localsettings.BROKER_PASSWORD djcelery.setup_loader() @@ -229,11 +203,7 @@ SOCIALACCOUNT_PROVIDERS = { AVATAR_STORAGE_DIR = MEDIA_ROOT + '/avatars/' ACCOUNT_LOGOUT_REDIRECT_URL = '/' -DSS_TEMP_PATH = localsettings.DSS_TEMP_PATH -DSS_LAME_PATH = localsettings.DSS_LAME_PATH -DSS_WAVE_PATH = localsettings.DSS_WAVE_PATH - -PIPELINE_YUI_BINARY = localsettings.PIPELINE_YUI_BINARY +PIPELINE_YUI_BINARY = "" PIPELINE = False PIPELINE_CSS = { 'defaults': { @@ -248,29 +218,21 @@ PIPELINE_CSS = { } INTERNAL_IPS = ('127.0.0.1', '86.44.166.21', '192.168.1.111') -GOOGLE_ANALYTICS_CODE = localsettings.GOOGLE_ANALYTICS_CODE - TASTYPIE_DATETIME_FORMATTING = 'rfc-2822' TASTYPIE_ALLOW_MISSING_SLASH = True -SENDFILE_BACKEND = localsettings.SENDFILE_BACKEND SENDFILE_ROOT = os.path.join(MEDIA_ROOT, 'mixes') SENDFILE_URL = '/media/mixes' -import mimetypes - mimetypes.add_type("text/xml", ".plist", False) -HTML_MINIFY = not localsettings.DEBUG +HTML_MINIFY = not DEBUG -EMAIL_HOST = localsettings.EMAIL_HOST -EMAIL_PORT = localsettings.EMAIL_PORT DEFAULT_FROM_EMAIL = 'DSS ChatBot ' DEFAULT_HTTP_PROTOCOL = 'http' EMAIL_BACKEND = 'djrill.mail.backends.djrill.DjrillBackend' -MANDRILL_API_KEY = localsettings.MANDRILL_API_KEY if DEBUG: import mimetypes @@ -282,25 +244,13 @@ if DEBUG: mimetypes.add_type("font/ttf", ".ttf", True) mimetypes.add_type("font/otf", ".otf", True) -# TODO(fergal.moran@gmail.com): #import localsettings - so all localsettings are part of import settings - REALTIME_HEADERS = { 'content-type': 'application/json' } -DBBACKUP_STORAGE = localsettings.DBBACKUP_STORAGE -DBBACKUP_TOKENS_FILEPATH = localsettings.DBBACKUP_TOKENS_FILEPATH -DBBACKUP_DROPBOX_APP_KEY = localsettings.DBBACKUP_DROPBOX_APP_KEY -DBBACKUP_DROPBOX_APP_SECRET = localsettings.DBBACKUP_DROPBOX_APP_SECRET -DBBACKUP_CLEANUP_KEEP = 5 - if 'test' in sys.argv: try: from test_settings import * except ImportError: pass -from paymentsettings import * - -GEOIP_PATH = localsettings.GEOIP_PATH -from pipelinesettings import * diff --git a/spa/audio.py b/spa/audio.py index f0b9ba3..9615737 100755 --- a/spa/audio.py +++ b/spa/audio.py @@ -1,10 +1,13 @@ import mimetypes import os import logging +import urlparse from django.conf.urls import url +import json from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseNotFound from django.core.servers.basehttp import FileWrapper +from nginx_signing.signing import UriSigner from sendfile import sendfile from dss import settings @@ -34,22 +37,49 @@ def download(request, mix_id): mix = Mix.objects.get(pk=mix_id) if mix is not None: if mix.download_allowed: - mix.add_download(request.user) audio_file = mix.get_absolute_path() filename, extension = os.path.splitext(audio_file) - response = HttpResponse(FileWrapper(open(audio_file)), - content_type=mimetypes.guess_type(audio_file)[0]) + + if os.path.exists(audio_file): + return sendfile( + request, + audio_file, + attachment=True, + attachment_filename='Deep South Sounds - %s%s' % ( + mix.title, extension + ) + ) + """ + signer = UriSigner(settings.DOWNLOAD_SIGNING_KEY) + response_url = urlparse.urljoin( + settings.DOWNLOAD_HOST, + signer.sign('/mixes/%s%s' % (mix.uid, extension))) + + response = HttpResponse(response_url, + content_type=mimetypes.guess_type(audio_file)[0], + mimetype='application/force-download') response['Content-Length'] = os.path.getsize(audio_file) - response['Content-Disposition'] = "attachment; filename=Deep South Sounds - %s%s" % ( - mix.title, extension) - return response + response['Content-Disposition'] = \ + "attachment; filename=\"Deep South Sounds - %s%s\"" % ( + mix.title, extension + ) + mix.add_download(request.user) + return HttpResponse( + json.dumps({ + 'url': response_url, + 'filename': "Deep South Sounds - %s%s\"" % ( + mix.title, extension + ) + }) + ) + """ else: return HttpResponse('Downloads not allowed for this mix', status=401) except Exception, ex: print ex - return Http404("Mix not found") + raise Http404("Mix not found") def start_streaming(request, mix_id): @@ -58,7 +88,7 @@ def start_streaming(request, mix_id): mix = Mix.objects.get(pk=mix_id) if mix is not None: mix.add_play(request.user) - #logger.debug('Found the mix (old method): %s' % mix.uid) + # logger.debug('Found the mix (old method): %s' % mix.uid) logger.debug('Found the mix (new method) %s' % mix.uid) filename = "%s/mixes/%s.mp3" % (here(settings.MEDIA_ROOT), mix.uid) logger.debug('Serving file: %s' % filename) diff --git a/spa/management/commands/test_schedules.py b/spa/management/commands/test_schedules.py new file mode 100644 index 0000000..0102037 --- /dev/null +++ b/spa/management/commands/test_schedules.py @@ -0,0 +1,19 @@ +from django.core.management.base import NoArgsCommand +from datetime import datetime +from spa.models import Show +from spa.models.show import ShowOverlapException + +DATE_FORMAT = '%d/%m/%Y %H:%M:%S' +START_DATE = datetime.strptime("28/04/2013 12:00:00", DATE_FORMAT) +END_DATE = datetime.strptime("28/04/2013 13:00:00", DATE_FORMAT) + + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + try: + Show.objects.all().delete() + Show(description="Test event one", start=START_DATE, end=END_DATE).save() + Show(description="Test event one", start=START_DATE, end=END_DATE).save() + + except Exception, ex: + print "Debug exception: %s" % ex.message diff --git a/spa/models/show.py b/spa/models/show.py index 576f90c..1650cec 100644 --- a/spa/models/show.py +++ b/spa/models/show.py @@ -13,6 +13,7 @@ class Show(Event): """ throw an exception if event overlaps with another event """ + import ipdb; ipdb.set_trace() overlaps = Show.objects.filter( Q(start__gte=self.start, end__lte=self.start) | Q(start__gte=self.end, end__lte=self.end) @@ -20,4 +21,4 @@ class Show(Event): if len(overlaps) != 0: raise ShowOverlapException() - return super(Show, self).save(force_insert, force_update, using, update_fields) \ No newline at end of file + return super(Show, self).save(force_insert, force_update, using, update_fields) diff --git a/static/js/app/lib/utils.coffee b/static/js/app/lib/utils.coffee index 5efd0d4..c86185f 100755 --- a/static/js/app/lib/utils.coffee +++ b/static/js/app/lib/utils.coffee @@ -1,4 +1,5 @@ -define ['jquery', 'bootstrap', 'toastr'], ($, bootstrap, toastr) -> +define ['jquery', 'lib/jquery.filedownload', 'bootstrap', 'toastr'], + ($, filedownload, bootstrap, toastr) -> modal: (url) -> return if $('#modal-header').length if url @@ -83,14 +84,24 @@ define ['jquery', 'bootstrap', 'toastr'], ($, bootstrap, toastr) -> v.toString 16 downloadURL: (url) -> - iframe = document.getElementById("hiddenDownloader") + """ + $.getJSON url, (data) => + $.fileDownload(data.url) + successCallback: (url) -> + alert "You just got a file download dialog or ribbon for this URL :" + url + return + + failCallback: (html, url) -> + alert "Your file download just failed for this URL:" + url + "\r\n" + "Here was the resulting error HTML: \r\n" + html + return + """ + iframe = document.getElementById("if_dl_misecure") if iframe is null iframe = document.createElement("iframe") - iframe.id = "hiddenDownloader" + iframe.id = "if_dl_misecure" iframe.style.visibility = "hidden" document.body.appendChild iframe iframe.src = url true - isMe: (id) -> id == com.podnoms.settings.currentUser diff --git a/static/js/app/lib/utils.js b/static/js/app/lib/utils.js index d1a0ad6..29255e2 100755 --- a/static/js/app/lib/utils.js +++ b/static/js/app/lib/utils.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript 1.4.0 (function() { - define(['jquery', 'bootstrap', 'toastr'], function($, bootstrap, toastr) { + define(['jquery', 'lib/jquery.filedownload', 'bootstrap', 'toastr'], function($, filedownload, bootstrap, toastr) { return { modal: function(url) { if ($('#modal-header').length) { @@ -104,11 +104,13 @@ }); }, downloadURL: function(url) { + "$.getJSON url, (data) =>\n $.fileDownload(data.url)\n successCallback: (url) ->\n alert \"You just got a file download dialog or ribbon for this URL :\" + url\n return\n\n failCallback: (html, url) ->\n alert \"Your file download just failed for this URL:\" + url + \"\r\n\" + \"Here was the resulting error HTML: \r\n\" + html\n return"; + var iframe; - iframe = document.getElementById("hiddenDownloader"); + iframe = document.getElementById("if_dl_misecure"); if (iframe === null) { iframe = document.createElement("iframe"); - iframe.id = "hiddenDownloader"; + iframe.id = "if_dl_misecure"; iframe.style.visibility = "hidden"; document.body.appendChild(iframe); } diff --git a/static/js/lib/jquery.filedownload.js b/static/js/lib/jquery.filedownload.js new file mode 100644 index 0000000..7b07219 --- /dev/null +++ b/static/js/lib/jquery.filedownload.js @@ -0,0 +1,445 @@ +/* +* jQuery File Download Plugin v1.4.2 +* +* http://www.johnculviner.com +* +* Copyright (c) 2013 - John Culviner +* +* Licensed under the MIT license: +* http://www.opensource.org/licenses/mit-license.php +* +* !!!!NOTE!!!! +* You must also write a cookie in conjunction with using this plugin as mentioned in the orignal post: +* http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/ +* !!!!NOTE!!!! +*/ + +(function($, window){ + // i'll just put them here to get evaluated on script load + var htmlSpecialCharsRegEx = /[<>&\r\n"']/gm; + var htmlSpecialCharsPlaceHolders = { + '<': 'lt;', + '>': 'gt;', + '&': 'amp;', + '\r': "#13;", + '\n': "#10;", + '"': 'quot;', + "'": 'apos;' /*single quotes just to be safe*/ + }; + +$.extend({ + // + //$.fileDownload('/path/to/url/', options) + // see directly below for possible 'options' + fileDownload: function (fileUrl, options) { + + //provide some reasonable defaults to any unspecified options below + var settings = $.extend({ + + // + //Requires jQuery UI: provide a message to display to the user when the file download is being prepared before the browser's dialog appears + // + preparingMessageHtml: null, + + // + //Requires jQuery UI: provide a message to display to the user when a file download fails + // + failMessageHtml: null, + + // + //the stock android browser straight up doesn't support file downloads initiated by a non GET: http://code.google.com/p/android/issues/detail?id=1780 + //specify a message here to display if a user tries with an android browser + //if jQuery UI is installed this will be a dialog, otherwise it will be an alert + // + androidPostUnsupportedMessageHtml: "Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.", + + // + //Requires jQuery UI: options to pass into jQuery UI Dialog + // + dialogOptions: { modal: true }, + + // + //a function to call while the dowload is being prepared before the browser's dialog appears + //Args: + // url - the original url attempted + // + prepareCallback: function (url) { }, + + // + //a function to call after a file download dialog/ribbon has appeared + //Args: + // url - the original url attempted + // + successCallback: function (url) { }, + + // + //a function to call after a file download dialog/ribbon has appeared + //Args: + // responseHtml - the html that came back in response to the file download. this won't necessarily come back depending on the browser. + // in less than IE9 a cross domain error occurs because 500+ errors cause a cross domain issue due to IE subbing out the + // server's error message with a "helpful" IE built in message + // url - the original url attempted + // + failCallback: function (responseHtml, url) { }, + + // + // the HTTP method to use. Defaults to "GET". + // + httpMethod: "GET", + + // + // if specified will perform a "httpMethod" request to the specified 'fileUrl' using the specified data. + // data must be an object (which will be $.param serialized) or already a key=value param string + // + data: null, + + // + //a period in milliseconds to poll to determine if a successful file download has occured or not + // + checkInterval: 100, + + // + //the cookie name to indicate if a file download has occured + // + cookieName: "fileDownload", + + // + //the cookie value for the above name to indicate that a file download has occured + // + cookieValue: "true", + + // + //the cookie path for above name value pair + // + cookiePath: "/", + + // + //the title for the popup second window as a download is processing in the case of a mobile browser + // + popupWindowTitle: "Initiating file download...", + + // + //Functionality to encode HTML entities for a POST, need this if data is an object with properties whose values contains strings with quotation marks. + //HTML entity encoding is done by replacing all &,<,>,',",\r,\n characters. + //Note that some browsers will POST the string htmlentity-encoded whilst others will decode it before POSTing. + //It is recommended that on the server, htmlentity decoding is done irrespective. + // + encodeHTMLEntities: true + + }, options); + + var deferred = new $.Deferred(); + + //Setup mobile browser detection: Partial credit: http://detectmobilebrowser.com/ + var userAgent = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase(); + + var isIos; //has full support of features in iOS 4.0+, uses a new window to accomplish this. + var isAndroid; //has full support of GET features in 4.0+ by using a new window. Non-GET is completely unsupported by the browser. See above for specifying a message. + var isOtherMobileBrowser; //there is no way to reliably guess here so all other mobile devices will GET and POST to the current window. + + if (/ip(ad|hone|od)/.test(userAgent)) { + + isIos = true; + + } else if (userAgent.indexOf('android') !== -1) { + + isAndroid = true; + + } else { + + isOtherMobileBrowser = /avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4)); + + } + + var httpMethodUpper = settings.httpMethod.toUpperCase(); + + if (isAndroid && httpMethodUpper !== "GET") { + //the stock android browser straight up doesn't support file downloads initiated by non GET requests: http://code.google.com/p/android/issues/detail?id=1780 + + if ($().dialog) { + $("
").html(settings.androidPostUnsupportedMessageHtml).dialog(settings.dialogOptions); + } else { + alert(settings.androidPostUnsupportedMessageHtml); + } + + return deferred.reject(); + } + + var $preparingDialog = null; + + var internalCallbacks = { + + onPrepare: function (url) { + + //wire up a jquery dialog to display the preparing message if specified + if (settings.preparingMessageHtml) { + + $preparingDialog = $("
").html(settings.preparingMessageHtml).dialog(settings.dialogOptions); + + } else if (settings.prepareCallback) { + + settings.prepareCallback(url); + + } + + }, + + onSuccess: function (url) { + + //remove the perparing message if it was specified + if ($preparingDialog) { + $preparingDialog.dialog('close'); + }; + + settings.successCallback(url); + + deferred.resolve(url); + }, + + onFail: function (responseHtml, url) { + + //remove the perparing message if it was specified + if ($preparingDialog) { + $preparingDialog.dialog('close'); + }; + + //wire up a jquery dialog to display the fail message if specified + if (settings.failMessageHtml) { + $("
").html(settings.failMessageHtml).dialog(settings.dialogOptions); + } + + settings.failCallback(responseHtml, url); + + deferred.reject(responseHtml, url); + } + }; + + internalCallbacks.onPrepare(fileUrl); + + //make settings.data a param string if it exists and isn't already + if (settings.data !== null && typeof settings.data !== "string") { + settings.data = $.param(settings.data); + } + + + var $iframe, + downloadWindow, + formDoc, + $form; + + if (httpMethodUpper === "GET") { + + if (settings.data !== null) { + //need to merge any fileUrl params with the data object + + var qsStart = fileUrl.indexOf('?'); + + if (qsStart !== -1) { + //we have a querystring in the url + + if (fileUrl.substring(fileUrl.length - 1) !== "&") { + fileUrl = fileUrl + "&"; + } + } else { + + fileUrl = fileUrl + "?"; + } + + fileUrl = fileUrl + settings.data; + } + + if (isIos || isAndroid) { + + downloadWindow = window.open(fileUrl); + downloadWindow.document.title = settings.popupWindowTitle; + window.focus(); + + } else if (isOtherMobileBrowser) { + + window.location(fileUrl); + + } else { + + //create a temporary iframe that is used to request the fileUrl as a GET request + $iframe = $("").appendTo("body"); + formDoc = getiframeDocument($iframe); + } + + formDoc.write("
" + formInnerHtml + "
" + settings.popupWindowTitle + ""); + $form = $(formDoc).find('form'); + } + + $form.submit(); + } + + + //check if the file download has completed every checkInterval ms + setTimeout(checkFileDownloadComplete, settings.checkInterval); + + + function checkFileDownloadComplete() { + + //has the cookie been written due to a file download occuring? + if (document.cookie.indexOf(settings.cookieName + "=" + settings.cookieValue) != -1) { + + //execute specified callback + internalCallbacks.onSuccess(fileUrl); + + //remove the cookie and iframe + document.cookie = settings.cookieName + "=; expires=" + new Date(1000).toUTCString() + "; path=" + settings.cookiePath; + + cleanUp(false); + + return; + } + + //has an error occured? + //if neither containers exist below then the file download is occuring on the current window + if (downloadWindow || $iframe) { + + //has an error occured? + try { + + var formDoc = downloadWindow ? downloadWindow.document : getiframeDocument($iframe); + + if (formDoc && formDoc.body != null && formDoc.body.innerHTML.length) { + + var isFailure = true; + + if ($form && $form.length) { + var $contents = $(formDoc.body).contents().first(); + + try { + if ($contents.length && $contents[0] === $form[0]) { + isFailure = false; + } + } catch (e) { + if (e && e.number == -2146828218) { + // IE 8-10 throw a permission denied after the form reloads on the "$contents[0] === $form[0]" comparison + isFailure = true; + } else { + throw e; + } + } + } + + if (isFailure) { + // IE 8-10 don't always have the full content available right away, they need a litle bit to finish + setTimeout(function () { + internalCallbacks.onFail(formDoc.body.innerHTML, fileUrl); + cleanUp(true); + }, 100); + + return; + } + } + } + catch (err) { + + //500 error less than IE9 + internalCallbacks.onFail('', fileUrl); + + cleanUp(true); + + return; + } + } + + + //keep checking... + setTimeout(checkFileDownloadComplete, settings.checkInterval); + } + + //gets an iframes document in a cross browser compatible manner + function getiframeDocument($iframe) { + var iframeDoc = $iframe[0].contentWindow || $iframe[0].contentDocument; + if (iframeDoc.document) { + iframeDoc = iframeDoc.document; + } + return iframeDoc; + } + + function cleanUp(isFailure) { + + setTimeout(function() { + + if (downloadWindow) { + + if (isAndroid) { + downloadWindow.close(); + } + + if (isIos) { + if (downloadWindow.focus) { + downloadWindow.focus(); //ios safari bug doesn't allow a window to be closed unless it is focused + if (isFailure) { + downloadWindow.close(); + } + } + } + } + + //iframe cleanup appears to randomly cause the download to fail + //not doing it seems better than failure... + //if ($iframe) { + // $iframe.remove(); + //} + + }, 0); + } + + + function htmlSpecialCharsEntityEncode(str) { + return str.replace(htmlSpecialCharsRegEx, function(match) { + return '&' + htmlSpecialCharsPlaceHolders[match]; + }); + } + + return deferred.promise(); + } +}); + +})(jQuery, this);