diff --git a/.gitignore b/.gitignore index c2ad1db..50d8c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,8 @@ node_modules credentials.js # webstorm project files -.idea \ No newline at end of file +.idea + +.DS_store +Thumbs.db +*.bak diff --git a/README-stg.md b/README-stg.md index 56bacc1..15cc34d 100644 --- a/README-stg.md +++ b/README-stg.md @@ -43,11 +43,11 @@ get model URNs - as explained in the Setup/Usage Instructions. ``` cp credentials_.js credentials.js ``` -* Replace the placeholder with your own keys in credentials.js, line #23 and #24
+* Replace the placeholders with your own keys in credentials.js, line #23 and #24
``` - credentials.ClientId = ''; + client_id: process.env.CONSUMERKEY || ''; - credentials.ClientSecret = ''; + client_secret: process.env.CONSUMERSECRET || ''; ``` * In file credentials.js line #26, replace the BaseUrl address by the staging server address
``` @@ -56,7 +56,7 @@ get model URNs - as explained in the Setup/Usage Instructions. * Upload one of your models to your account and get its URN using another workflow sample, for example, - [this workflow sample in .Net WPF application](https://github.com/Developer-Autodesk/workflow-wpf-view.and.data.api) if you are using windows - or [this workflow sample in Mac OS Swift](https://github.com/Developer-Autodesk/workflow-macos-swift-view.and.data.api) if you are using Mac - - or this [WEB page](http://javalmvwalkthrough-vq2mmximxb.elasticbeanstalk.com/) + - or this [WEB page](http://models.autodesk.io/) or this [one](http://javalmvwalkthrough-vq2mmximxb.elasticbeanstalk.com/) * Copy the URN which was generated in the previous step in file /www/index.js at line #18
``` var defaultUrn = ''; diff --git a/README.md b/README.md index 890eed8..5afcb32 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,16 @@ get model URNs - as explained in the Setup/Usage Instructions. ``` cp credentials_.js credentials.js ``` -* Replace the placeholder with your own keys in credentials.js, line #23 and #24
+* Replace the placeholders with your own keys in credentials.js, line #23 and #24
``` - credentials.ClientId = ''; + client_id: process.env.CONSUMERKEY || '', - credentials.ClientSecret = ''; + client_secret: process.env.CONSUMERSECRET || '', ``` * Upload one of your models to your account and get its URN using another workflow sample, for example, - [this workflow sample in .Net WPF application](https://github.com/Developer-Autodesk/workflow-wpf-view.and.data.api) if you are using windows - or [this workflow sample in Mac OS Swift](https://github.com/Developer-Autodesk/workflow-macos-swift-view.and.data.api) if you are using Mac - - or this [WEB page](http://javalmvwalkthrough-vq2mmximxb.elasticbeanstalk.com/) + - or this [WEB page](http://models.autodesk.io/) or this [one](http://javalmvwalkthrough-vq2mmximxb.elasticbeanstalk.com/) * Copy the URN which was generated in the previous step in file /www/index.js at line #18
``` var defaultUrn = ''; diff --git a/credentials_.js b/credentials_.js index 7faa047..fac0c4f 100644 --- a/credentials_.js +++ b/credentials_.js @@ -15,15 +15,21 @@ // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. ///////////////////////////////////////////////////////////////////////////////// +var credentials ={ -var credentials ={} ; + credentials: { + // Replace placeholder below by the Consumer Key and Consumer Secret you got from + // http://developer.autodesk.com/ for the production server + client_id: process.env.CONSUMERKEY || '', + client_secret: process.env.CONSUMERSECRET || '', + grant_type: 'client_credentials' + }, + + // If you which to use the Autodesk View & Data API on the staging server, change this url + BaseUrl: 'https://developer.api.autodesk.com', + Version: 'v1' +} ; -// Replace placeholder below by the Consumer Key and Consumer Secret you got from -// http://developer.autodesk.com/ for the production server -credentials.ClientId ='' ; -credentials.ClientSecret ='' ; - -// If you which to use the Autodesk View & Data API on the staging server, change this url -credentials.BaseUrl = 'https://developer.api.autodesk.com' ; +credentials.Authentication =credentials.BaseUrl + '/authentication/' + credentials.Version + '/authenticate' module.exports =credentials ; diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/package.json b/package.json index 01fb0f0..89b8c26 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,33 @@ { - "name": "AdnViewerBasic", - - "version": "0.0.0", - - "dependencies": { - "express": "*", - "request": "*", - "serve-favicon": "*" - } -} \ No newline at end of file + "name": "AdnViewerBasic", + "description": "A basic node.js server sample", + "version": "1.0.0", + "dependencies": { + "serve-favicon": ">= 2.2.0", + "express": ">= 4.12.3", + "request": ">= 2.55.0", + "body-parser": ">= 1.11.0", + "formidable": ">= 1.0.17", + "unirest": ">= 0.4.0", + "async": ">= 0.9.0" + }, + "files": [ + "LICENSE", + "README.md" + ], + "engines": { + "node": ">= 0.10.0" + }, + "contributors": [ + "Philippe Leefsma ", + "Cyrille Fauvel " + ], + "license": "MIT", + "scripts": { + "start": "node server.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/Developer-Autodesk/workflow-node.js-view.and.data.api.git" + } +} diff --git a/routes/api.js b/routes/api.js index 408f063..7aaa419 100644 --- a/routes/api.js +++ b/routes/api.js @@ -15,32 +15,25 @@ // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. ///////////////////////////////////////////////////////////////////////////////// -var credentials = require('../credentials'); +var credentials =(require ('fs').existsSync ('credentials.js') ? + require('../credentials') + : (console.log ('No credentials.js file present, assuming using CONSUMERKEY & CONSUMERSECRET system variables.'), require('../credentials_'))) ; +var express =require ('express') ; +var request =require ('request') ; -var express = require('express'); -var request = require('request'); - -var router = express.Router(); +var router =express.Router () ; /////////////////////////////////////////////////////////////////////////////// // Generates access token /////////////////////////////////////////////////////////////////////////////// -router.get('/token', function (req, res) { - var params = { - client_id: credentials.ClientId, - client_secret: credentials.ClientSecret, - grant_type: 'client_credentials' - } - - request.post( - credentials.BaseUrl + '/authentication/v1/authenticate', - { form: params }, - +router.get ('/token', function (req, res) { + request.post ( + credentials.Authentication, + { form: credentials.credentials }, function (error, response, body) { - if (!error && response.statusCode == 200) { - res.send(body); - } - }); -}); + if ( !error && response.statusCode == 200 ) + res.send (body) ; + }) ; +}) ; -module.exports = router; +module.exports =router ; diff --git a/routes/lmv.js b/routes/lmv.js new file mode 100644 index 0000000..e639c29 --- /dev/null +++ b/routes/lmv.js @@ -0,0 +1,283 @@ +// +// Copyright (c) Autodesk, Inc. All rights reserved +// +// Node.js server workflow +// by Cyrille Fauvel - Autodesk Developer Network (ADN) +// January 2015 +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +var credentials =(require ('fs').existsSync ('credentials.js') ? + require('../credentials') + : (console.log ('No credentials.js file present, assuming using CONSUMERKEY & CONSUMERSECRET system variables.'), require('../credentials_'))) ; +var express =require ('express') ; +var request =require ('request') ; +// unirest (http://unirest.io/) or SuperAgent (http://visionmedia.github.io/superagent/) +var unirest =require('unirest') ; +var events =require('events') ; +var util =require ('util') ; +var path =require ('path') ; +var fs =require ('fs') ; + +// LMV object +function Lmv (bucketName) { + events.EventEmitter.call (this) ; + this.bucket =bucketName ; + this.creds =credentials ; +} +//Lmv.prototype.__proto__ =events.EventEmitter.prototype ; +util.inherits (Lmv, events.EventEmitter) ; + +/*static*/ Lmv.getToken =function () { + try { + var data =fs.readFileSync ('data/token.json') ; + var authResponse =JSON.parse (data) ; + return (authResponse.access_token) ; + } catch ( err ) { + console.log (err) ; + } + return ('') ; +} ; + +// POST /authentication/v1/authenticate +/*static*/ Lmv.refreshToken =function () { + console.log ('Refreshing Autodesk Service token') ; + unirest.post (credentials.Authentication) + .header ('Accept', 'application/json') + //.type ('application/x-www-form-urlencoded') + .send (credentials.credentials) + .end (function (response) { + try { + if ( response.statusCode != 200 ) + throw response ; + var authResponse =response.body ; + console.log ('Token: ' + JSON.stringify (authResponse)) ; + //authResponse.expires_at =Math.floor (Date.now () / 1000) + authResponse.expires_in ; + fs.writeFile ('data/token.json', JSON.stringify (authResponse), function (err) { + if ( err ) + throw err ; + }) ; + } catch ( err ) { + fs.unlinkSync ('data/token.json') ; + console.log ('Token: ERROR! (' + response.statusCode + ')') ; + } + }) + ; +} ; + +// GET /oss/v1/buckets/:bucket/details +Lmv.prototype.checkBucket =function () { + var self =this ; + unirest.get (self.creds.BaseUrl + '/oss/v1/buckets/' + self.bucket + '/details') + .header ('Accept', 'application/json') + .header ('Content-Type', 'application/json') + .header ('Authorization', 'Bearer ' + Lmv.getToken ()) + //.query (params) + .end (function (response) { + try { + if ( response.statusCode != 200 ) + throw response ; + self.emit ('success', response.raw_body) ; + } catch ( err ) { + self.emit ('fail', err) ; + } + }) + ; + return (this) ; +} ; + +// POST /oss/v1/buckets +Lmv.prototype.createBucket =function (policy) { + policy =policy || 'transient' ; + var self =this ; + unirest.post (self.creds.BaseUrl + '/oss/v1/buckets') + .header ('Accept', 'application/json') + .header ('Content-Type', 'application/json') + .header ('Authorization', 'Bearer ' + Lmv.getToken ()) + .send ({ 'bucketKey': self.bucket, 'policy': policy }) + .end (function (response) { + try { + if ( response.statusCode != 200 || !response.raw_body.hasOwnProperty ('key') ) + throw response ; + self.emit ('success', response.raw_body) ; + } catch ( err ) { + self.emit ('fail', err) ; + } + }) + ; + return (this) ; +} ; + +Lmv.prototype.createBucketIfNotExist =function (policy) { + policy =policy || 'transient' ; + var self =this ; + + unirest.get (self.creds.BaseUrl + '/oss/v1/buckets/' + self.bucket + '/details') + .header ('Accept', 'application/json') + .header ('Content-Type', 'application/json') + .header ('Authorization', 'Bearer ' + Lmv.getToken ()) + //.query (params) + .end (function (response) { + try { + if ( response.statusCode != 200 ) + throw response ; + try { + self.emit ('success', response.raw_body) ; + } catch ( err ) { + } + } catch ( err ) { + //- We need to create one if error == 404 (404 Not Found) + if ( Number.isInteger (err.statusCode) && err.statusCode == 404 ) { + unirest.post (self.creds.BaseUrl + '/oss/v1/buckets') + .header ('Accept', 'application/json') + .header ('Content-Type', 'application/json') + .header ('Authorization', 'Bearer ' + Lmv.getToken ()) + .send ({ 'bucketKey': self.bucket, 'policy': policy }) + .end (function (response) { + try { + if ( response.statusCode != 200 || !response.raw_body.hasOwnProperty ('key') ) + throw response ; + try { + self.emit ('success', response.raw_body) ; + } catch ( err ) { + } + } catch ( err ) { + self.emit ('fail', err) ; + } + }) + ; + } else { + self.emit ('fail', err) ; + } + } + }) + ; + return (this) ; +} ; + +// PUT /oss/v1/buckets/:bucket/objects/:filename +Lmv.prototype.uploadFile =function (filename) { + var self =this ; + var serverFile =path.normalize (__dirname + '/../' + filename) ; + var localFile =path.basename (filename) ; + + var file =fs.readFile (serverFile, function (err, data) { + if ( err ) + return (self.emit ('fail', err)) ; + + var endpoint ='/oss/v1/buckets/' + self.bucket + '/objects/' + localFile.replace (/ /g, '+') ; + unirest.put (self.creds.BaseUrl + endpoint) + .headers ({ + 'Accept': 'application/json', + 'Content-Type': 'application/octet-stream', + 'Authorization': ('Bearer ' + Lmv.getToken ()) + }) + .send (data) + .end (function (response) { + try { + if ( response.statusCode != 200 ) + throw response ; + try { + self.emit ('success', response.raw_body) ; + } catch ( err ) { + } + } catch ( err ) { + self.emit ('fail', err) ; + } + }) + ; + }) ; + return (this) ; +} ; + +// POST /viewingservice/v1/register +Lmv.prototype.register =function (urn) { + var self =this ; + var desc ={ 'urn': new Buffer (urn).toString ('base64') } ; + + unirest.post (self.creds.BaseUrl + '/viewingservice/v1/register') + .headers ({ + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': ('Bearer ' + Lmv.getToken ()) + }) + .send (desc) + .end (function (response) { + try { + if ( response.statusCode != 200 && response.statusCode != 201 ) + throw response ; + try { + self.emit ('success', { 'urn': desc.urn, 'response': response.body }) ; + } catch ( err ) { + } + } catch ( err ) { + self.emit ('fail', err) ; + } + }) + ; + return (this) ; +} ; + +Lmv.prototype.status =function (urn, params) { + var self =this ; + params =params || {} ; + + var endpoint ='/viewingservice/v1/' + urn + '/status' ; + unirest.get (self.creds.BaseUrl + endpoint) + .headers ({ + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': ('Bearer ' + Lmv.getToken ()) + }) + .query (params) + .end (function (response) { + try { + if ( response.statusCode != 200 ) + throw response ; + try { + self.emit ('success', response.body) ; + } catch ( err ) { + } + } catch ( err ) { + self.emit ('fail', err) ; + } + }) + ; + return (this) ; +} ; + +var router =express.Router () ; +router.Lmv =Lmv ; +module.exports =router ; + +// Utility +if ( !Number.isInteger ) { + Number.isInteger =function isInteger (nVal) { + return ( + typeof nVal === 'number' + && isFinite (nVal) + && nVal > -9007199254740992 + && nVal < 9007199254740992 + && Math.floor (nVal) === nVal + ) ; + } ; +} + +// Initialization +function initializeApp () { + var seconds =1700 ; // Service returns 1799 seconds bearer token + setInterval (Lmv.refreshToken, seconds * 1000) ; + Lmv.refreshToken () ; // and now! +} +initializeApp () ; diff --git a/routes/upload.js b/routes/upload.js new file mode 100644 index 0000000..1b8fe18 --- /dev/null +++ b/routes/upload.js @@ -0,0 +1,141 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Autodesk, Inc. All rights reserved +// Written by Cyrille Fauvel, 2015 - ADN/Developer Technical Services +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +/////////////////////////////////////////////////////////////////////////////// + +var express =require ('express') ; +var bodyParser =require ('body-parser') ; +var formidable = require('formidable') +var fs =require ('fs') ; +var async =require ('async') ; +var lmv =require ('./lmv.js') ; + +var router =express.Router () ; +router.use (bodyParser.json ()) ; + +router.post ('/file', function (req, res) { + /*req + .pipe (fs.createWriteStream ('data/' + req.headers ['x-file-name'])) + .on ('finish', function (err) { + res.json ({ 'name': req.headers ['x-file-name'] }) ; + }) + .on ('error', function (err) { + res.status (500).end () ; + }) + ;*/ + var filename ='' ; + + var form =new formidable.IncomingForm () ; + form.uploadDir ='data' ; + form + .on ('field', function (field, value) { + console.log (field, value) ; + }) + .on ('file', function (field, file) { + console.log (field, file) ; + fs.rename (file.path, form.uploadDir + '/' + file.name) ; + filename =file.name ; + }) + .on ('end', function () { + console.log ('-> upload done') ; + if ( filename == '' ) + res.status (500).end ('No file submitted!') ; + res.json ({ 'name': filename }) ; + }) + ; + form.parse(req); +}) ; + +router.post ('/translate', function (req, res) { + var filename ='data/' + req.body.name ; + var bucket = + 'model' + + new Date ().toISOString ().replace (/T/, '-').replace (/:+/g, '-').replace (/\..+/, '') + + '-' + lmv.Lmv.getToken ().toLowerCase ().replace (/\W+/g, '') ; + var policy ='transient' ; + + async.waterfall ([ + function (callbacks1) { + console.log ('createBucketIfNotExist') ; + new lmv.Lmv (bucket).createBucketIfNotExist (policy) + .on ('success', function (data) { + console.log ('Bucket already or now exist!') ; + callbacks1 (null, data) ; + }) + .on ('fail', function (err) { + console.log ('Failed to create bucket!') ; + callbacks1 (err) ; + }) + ; + }, + + function (arg1, callbacks2) { + console.log ('async upload') ; + new lmv.Lmv (bucket).uploadFile (filename) + .on ('success', function (data) { + console.log (filename + ' uploaded.') ; + callbacks2 (null, data) ; + }) + .on ('fail', function (err) { + console.log ('Failed to upload ' + filename + '!') ; + callbacks2 (err) ; + }) + ; + }, + + function (arg1, callbacks3) { + console.log ('Launching translation') ; + var urn =JSON.parse (arg1).objects [0].id ; + new lmv.Lmv (bucket).register (urn) + .on ('success', function (data) { + console.log ('Translation requested.') ; + callbacks3 (null, data) ; + }) + .on ('fail', function (err) { + console.log ('Failed to request translation!') ; + callbacks3 (err) ; + }) + ; + } + + ], function (err, results) { + if ( err != null ) { + if ( err.hasOwnProperty ('statusCode') && err.statusCode != 200 ) + return (res.status (err.statusCode).send (err.body.reason)) ; + if ( !err.raw_body.hasOwnProperty ('key') ) + return (res.status (500).send ('The server did not return a valid key')) ; + return (res.status (500).send ('An unknown error occurred!')) ; + } + + res.json (results) ; + }) ; + +}) ; + +router.get ('/translate/:urn/progress', function (req, res) { + var urn =req.params.urn ; + new lmv.Lmv ('').status (urn) + .on ('success', function (data) { + console.log (data.progress) ; + res.json (data) ; + }) + .on ('fail', function (err) { + res.status (404).end () ; + }) + ; +}) ; + +module.exports =router ; \ No newline at end of file diff --git a/server.js b/server.js index 989f4cf..ce315fd 100644 --- a/server.js +++ b/server.js @@ -17,6 +17,7 @@ ///////////////////////////////////////////////////////////////////////////////// var favicon = require('serve-favicon'); var api = require('./routes/api'); +var upload = require('./routes/upload'); var express = require('express'); var app = express(); @@ -24,6 +25,7 @@ var app = express(); app.use('/', express.static(__dirname + '/www')); app.use(favicon(__dirname + '/www/images/favicon.ico')); app.use('/api', api); +app.use('/api', upload); app.set('port', process.env.PORT || 3000); diff --git a/www/images/Thumbs.db b/www/images/Thumbs.db deleted file mode 100644 index 02d42c8..0000000 Binary files a/www/images/Thumbs.db and /dev/null differ diff --git a/www/upload.html b/www/upload.html new file mode 100644 index 0000000..2e2bebc --- /dev/null +++ b/www/upload.html @@ -0,0 +1,76 @@ + + + + ADN Viewer Demo (client upload) + + + + + + + + + + + + + +
+
+
+

Upload and translate a file

+
+
+ +
+ + +
+
+
+
+
+ +
+
+
+

My URNs

+
+
+
+
+ +
+ +
+ +
+ My URN list +
Click on a urn below to launch the viewer
+
+
+
+
+ + + diff --git a/www/upload.js b/www/upload.js new file mode 100644 index 0000000..9ef262f --- /dev/null +++ b/www/upload.js @@ -0,0 +1,107 @@ +///////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Autodesk, Inc. All rights reserved +// Written by Cyrille Fauvel, 2015 - ADN/Developer Technical Services +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +///////////////////////////////////////////////////////////////////////////////// + +$(document).ready (function () { + + $('#btnTranslateThisOne').click (function (evt) { + var files =document.getElementById ('files').files ; + if ( files.length == 0 ) + return ; + + $.each (files, function (key, value) { + var data =new FormData () ; + data.append (key, value) ; + + $.ajax ({ + url: 'http://' + window.location.host + '/api/file', + type: 'post', + headers: { 'x-file-name': value.name }, + data: data, + cache: false, + //dataType: 'json', + processData: false, // Don't process the files + contentType: false, // Set content type to false as jQuery will tell the server its a query string request + complete: null + }).done (function (data) { + $('#msg').text (value.name + ' file uploaded on your server') ; + translate (data) ; + }).fail (function (xhr, ajaxOptions, thrownError) { + $('#msg').text (value.name + ' upload failed!') ; + }) ; + }) ; + + }) ; + + $('#btnAddThisOne').click (function (evt) { + var urn =$('#urn').val ().trim () ; + if ( urn == '' ) + return ; + AddThisOne (urn) ; + }) ; + +}) ; + +function AddThisOne (urn) { + var id =urn.replace (/=+/g, '') ; + $('#list').append ('
' + + '' + + '
' + ) ; + $('#' + id).click (function (evt) { + window.open ('/?urn=' + $(this).text (), '_blank') ; + }) ; +} + +function translate (data) { + $('#msg').text (data.name + ' translation request...') ; + $.ajax ({ + url: '/api/translate', + type: 'post', + data: JSON.stringify (data), + timeout: 0, + contentType: 'application/json', + complete: null + }).done (function (response) { + $('#msg').text (data.name + ' translation requested...') ; + setTimeout (function () { translateProgress (response.urn) ; }, 5000) ; + }).fail (function (xhr, ajaxOptions, thrownError) { + $('#msg').text (data.name + ' translation request failed!') ; + }) ; +} + +function translateProgress (urn) { + $.ajax ({ + url: '/api/translate/' + urn + '/progress', + type: 'get', + data: null, + contentType: 'application/json', + complete: null + }).done (function (response) { + if ( response.progress == 'complete' ) { + AddThisOne (response.urn) ; + $('#msg').text ('') ; + } else { + var name =window.atob (urn) ; + var filename =name.replace (/^.*[\\\/]/, '') ; + $('#msg').text (filename + ': ' + response.progress) ; + setTimeout (function () { translateProgress (urn) ; }, 500) ; + } + }).fail (function (xhr, ajaxOptions, thrownError) { + $('#msg').text ('Progress request failed!') ; + }) ; +} \ No newline at end of file