· 7 years ago · Jan 22, 2019, 04:58 AM
1//A JSON object that contains the string translations retrieved according to the user's language setting.
2var languageFile = {};
3//A JSON object that contains the string translations for the default language file (assumed to be English).
4var defaultLanguageFile = {};
5// convert-readme.txt file with license informations
6var convertReadmeFile = "";
7//The number of asynchronous calls that have returned from a given batch of calls.
8var returnedCalls = 0;
9//The number of asynchronous calls that are expected to be made for a given batch of calls.
10var expectedCalls = 0;
11// Reload side after overlay dialog has been closed
12var setReloadSide = false;
13// detect safari browser
14var browser = "other";
15// detect Mac OS
16var os = "other";
17
18//The currently selected left navigation item.
19var activeNav;
20//A boolean indicating whether or not the user has made any changes to a page of configuration settings.
21var changesMade = false;
22//A boolean indicating whether or not the user has clicked an input field
23var inputFieldClicked = false;
24//The handler to be called if the user elects to save the changes he made.
25var saveHandler;
26
27//An object containing all the data for the Status page.
28var statusData = {};
29var statusDataOrig = {};
30//The timer that automatically updates the a page at a regular increment.
31var updateTimer;
32//The interval at which to run the update timer, in milliseconds.
33var updateTimerInterval = 30000;
34// the status page is updated every 10 seconds 3 times. Then the timer is set to updateTimerInterval.
35var updateTimerInterval1 = 2000;
36var updateTimerInterval1Count = 0;
37// The timer that is initiated after pressing the log in or register button to the myTwonky portal
38var portalTimer;
39//The interval at which to run the portal timer, in milliseconds.
40//This variable gets the values of portalTimerInterval 1, then 2 and 3.
41var portalTimerInterval;
42// Counts how many times the timer was called. The value 0 indicates that no timer is started.
43var portalTimerIntervalCount = 0;
44// First (initial) timer interval and how many times he is called
45var portalTimerInterval1 = 40000;
46var portalTimerInterval1Count = 1;
47// Second timer interval and how many times he is called. The value must be different to 1 and 3.
48var portalTimerInterval2 = 15000;
49var portalTimerInterval2Count = 3;
50// Third timer interval and how many times he is called. The value must be different to 1 and 2.
51var portalTimerInterval3 = 30000;
52var portalTimerInterval3Count = 5;
53// timer which clear the background spinner after 5 seconds
54var loadingGraficActiv;
55
56var noMediaFusionServer = false;
57var noWebDavServer = false;
58var myTwonkyDisabled = false;
59
60// 1: browse shared folder, 2: browse sync folder
61var folderBrowseDialogMsg = 1;
62
63//An object containing all the data for the Setup page.
64var setup = {};
65var setupOrig = {};
66//The user's currently selected navigation type for the defaultview property.
67var selectedNavType;
68//An object containing all the data for the Sharing page.
69var sharing = {};
70var sharingOrig = {};
71//update receiver list
72var sharingReceiverChanged = false;
73//An object containing all the data for the Aggregation page.
74var aggregation = {};
75var aggregationOrig = {};
76//A list of aggregation servers the user has made modifications to. Tracked for optimization to avoid
77//sending updates to TwonkyServer when data is saved except for servers the user has changed.
78var changedServers = {};
79//An object containing all the data for the Advanced page.
80var advanced = {};
81var advancedOrig = {};
82//A list of media receivers the user has made modifications to. Tracked for optimization to avoid
83//sending updates to TwonkyServer when data is saved except for receivers the user has changed.
84var changedReceivers = {};
85//The last media id (corresponding to video, music, or photo) the user visited.
86var lastMediaId;
87
88
89$(window).bind("hashchange", function(e){
90 $("#leftNavContainer").show();
91 switch (e.fragment) {
92 case "":
93 case "status":
94 checkChanges("loadStatus();");
95 populateSettingsNav();
96 break;
97 case "setup":
98 checkChanges("loadSetup();");
99 populateSettingsNav();
100 break;
101 case "sharing":
102 checkChanges("loadSharing();");
103 populateSettingsNav();
104 break;
105 case "aggregation":
106 checkChanges("loadAggregation();");
107 populateSettingsNav();
108 break;
109 case "synchronization":
110 checkChanges("loadSynchronization();");
111 break;
112 case "advanced":
113 checkChanges("loadAdvanced();");
114 populateSettingsNav();
115 break;
116 case "video":
117 loadMediaBrowse("0$3");
118 lastMediaId = "0$3";
119 break;
120 case "music":
121 loadMediaBrowse("0$1");
122 lastMediaId = "0$1";
123 break;
124 case "photo":
125 loadMediaBrowse("0$2");
126 lastMediaId = "0$2";
127 break;
128 case "licenseinfo":
129 $("#leftNavContainer").hide();
130 $(".serverSettingsContentWrapper").html(getString("mpeglicense") + "<br /><br />" + getString("copyright") + "<br /><br /><br /><br />" +
131 convertReadmeFile + "<br /><br />" +
132 "<BR><BR>License information is provided here: <br /><br /> " +
133 "<a class='inlineLink' href='http://jquery.org/license/' >http://jquery.org/license/</a> (click on 'MIT License' link)<br /><br />" +
134 "<a class='inlineLink' href='http://benalman.com/about/license/' >http://benalman.com/about/license/</a> <br />");
135 break;
136 default:
137 var paramPieces = e.fragment.split("&");
138 var id = paramPieces[0].split("=")[1];
139 var startPage = paramPieces[1].split("=")[1];
140 var count = paramPieces[2].split("=")[1];
141 var mediaId = id.substring(0, 3);
142 if (mediaId != lastMediaId) {
143 loadMediaBrowse(mediaId, function(){
144 loadMediaContents(id, startPage, count, false);
145 });
146 }
147 else {
148 loadMediaContents(id, startPage, count, false);
149 }
150 lastMediaId = mediaId;
151 break;
152 }
153});
154
155function browserIdentification() {
156 browser = "other";
157 // special treating for Safari
158 if (navigator.userAgent.indexOf("Safari") != -1) browser = "Safari";
159 if (navigator.userAgent.indexOf("KHTML") != -1) browser = "Safari";
160}
161function osIdentification() {
162 os = navigator.platform;
163 if (navigator.platform.indexOf("Mac") != -1) os = "Mac";
164}
165
166function navigateTo(params){
167 window.location.href = $.param.fragment(window.location.href, params, 2);
168}
169
170function getLanguageFile(){
171 // read server language file
172 $.ajax("/webconfig/strings-" + statusData["language"] + ".json", {
173 "success": function(data){
174 languageFile = parseJson(data);
175 },
176 "error": function(){
177 $.ajax("/webconfig/strings-en.json", function(data){
178 languageFile = parseJson(data);
179 });
180 }
181 });
182}
183function getConvertReadmeFile(){
184 makeGetRequest("/webconfig/convert-readme.txt", {}, function(data){
185 convertReadmeFile = data;
186 convertReadmeFile = convertReadmeFile.replace(/(\r\n)|(\r)|(\n)/g, "<BR>");
187 });
188}
189
190//Get the value of a string given a key from the localized string translations.
191//key: The key to retrieve a string for.
192//getLong: Default false. If true, retrieve longValue from the translation object rather than value. longValue can be
193//used to store longer text that shouldn't necessarily always be displayed.
194function getString(key, getLong){
195 if (languageFile[key]) {
196 return (getLong) ? (languageFile[key].longValue) : (languageFile[key].value);
197 } else {
198 if (defaultLanguageFile[key]) {
199 return (getLong) ? (defaultLanguageFile[key].longValue) : (defaultLanguageFile[key].value);
200 } else {
201 return "";
202 }
203 }
204}
205
206//Replace the contents of all elements in html that have a "string" attribute with the matching value from the
207//translation file.
208//html: The HTML to perform the replacement on.
209function replaceStrings(html){
210 var stringElements = $("[string]", html);
211
212 stringElements.each(function(i){
213 $(this).html(getString($(this).attr("string")));
214 });
215}
216
217
218// show or hide the content of subheaders.
219// The layout of each page will be saved. If the user comes back to a page the layout has not changed.
220function showToggleButtons(html){
221 if (!(document.cookie)) return;
222 var buttonElements = $(".toggleButton", html);
223 buttonElements.each(function(i){
224 var elem = $(this);
225 var parent = elem.parents(".boxHeader");
226 var nextHeader = $(parent).next();
227 var id = elem.attr("id");
228 var c = document.cookie;
229 var a = c.split(";");
230 for (var j=0;j<a.length;j++) {
231 var cookieID = a[j].substring(0,a[j].indexOf("="));
232 var cookieValue = a[j].substring(a[j].indexOf("=")+1, a[j].length);
233 if (cookieID.indexOf(id) >= 0) {
234 if (cookieValue.indexOf("show") >= 0) {
235 nextHeader.show();
236 if (nextHeader.hasClass("hideSubheaderBody")) nextHeader.removeClass("hideSubheaderBody"); // for I.E.
237 $(".toggleText", elem).text(getString("hide"));
238 elem.removeClass("hidden");
239 elem.addClass("showing");
240 }
241 }
242 }
243 });
244}
245
246//Call a handler function for each element in html that has a "key" attribute to display data.
247//html: The HTML to perform the replacement on.
248//responseData: The data object to retrieve the data from.
249//handler: The handler that should be called when an element with a "key" attribute is discovered. Handlers should
250//have the function signature (element, key, data) where element is the affected element, key is the data's key,
251//and data is the data collection object.
252function replaceData(html, responseData, handler){
253 var dataElements = $("[key]", html);
254 dataElements.each(function(i){
255 var dataElement = $(this);
256 var value = responseData[dataElement.attr("key")];
257 if (handler) {
258 handler(dataElement, dataElement.attr("key"), value);
259 }
260 else {
261 dataElement.html(value);
262 }
263
264 });
265}
266
267//Split data using a separator character and store the resulting array.
268//responseData: The data to split.
269//dataCollection: The data object in which to store the data.
270//dataKey: The key used to store the data.
271//dataSeparator: The separator character.
272function parseData(responseData, dataCollection, dataCollectionOrig, dataKey, dataSeparator){
273 var responsePieces = responseData.split(dataSeparator);
274 dataCollection[dataKey] = responsePieces;
275 dataCollectionOrig[dataKey] = responsePieces;
276}
277
278//Split a collection of data that is in name/value pair form (e.g. /rpc/get_all) and store it in a data object.
279//The key becomes the first part of the split, and the value becomes the second (v=0 would be stored as {"v": 0}).
280//responseData: The data to split.
281//dataCollection: The data object in which to store the data. The data can be changed by the user.
282//dataCollectionOrig: The data object in which to store the data. These data are not changed by the user.
283//separatorChar: The character that separates the name/value pairs.
284function parseSeparatedData(responseData, dataCollection, dataCollectionOrig, separatorChar){
285 var responsePieces = responseData.split("\n");
286 $.each(responsePieces, function(i, value){
287 var pieceArray = value.split(separatorChar);
288 if (pieceArray.length == 2) {
289 var cleanedData = pieceArray[1].replace(/\r/g, "");
290 dataCollection[pieceArray[0]] = cleanedData;
291 dataCollectionOrig[pieceArray[0]] = cleanedData;
292 }
293 else {
294 var responseData = new Array(pieceArray.length - 1);
295 $.each(pieceArray, function(i, value){
296 if (pieceArray[i + 1]) {
297 var cleanedData = pieceArray[i + 1].replace(/\r/g, "");
298 responseData[i] = cleanedData;
299 }
300 });
301 dataCollection[pieceArray[0]] = responseData;
302 dataCollectionOrig[pieceArray[0]] = responseData;
303 }
304 });
305}
306
307//A generic wrapper for making AJAX GET requests.
308//url: The url to make the request to.
309//params: A collection of objects to be passed as querystring arguments. Use the format {"key": value}. For example,
310//[{"uuid": 1234}, {"example": true}] will be passed as ?uuid=1234&example=true in the querystring.
311//callback: The callback to be called after the request finishes.
312function makeGetRequest(url, params, callback){
313 var urlParams = "";
314 var separatorChar = "?";
315 if (params) {
316 $.each(params, function(i, value){
317 urlParams += separatorChar + i + "=" + value;
318 separatorChar = "&";
319 });
320 }
321 $.get(url + urlParams, function(response){
322 if (callback) {
323 callback(response);
324 }
325 });
326}
327
328//A generic wrapper for making AJAX POST requests.
329//url: The url to make the request to.
330//params: A collection of objects to be passed as querystring arguments. Use the format {"key": value}. For example,
331//[{"uuid": 1234}, {"example": true}] will be passed as ?uuid=1234&example=true in the querystring.
332//data: The data to be passed during the POST request.
333//callback: The callback to be called after the request finishes.
334function makePostRequest(url, params, data, callback){
335 var urlParams = "";
336 var separatorChar = "?";
337 if (params) {
338 $.each(params, function(i, value){
339 urlParams += separatorChar + i + "=" + value;
340 separatorChar = "&";
341 });
342 }
343 $.post(url + urlParams, data, function(response){
344 if (callback) {
345 callback(response);
346 }
347 });
348}
349
350function showLoadingGraphic(){
351 $(".serverSettingsContentWrapper").addClass("loading");
352 if (loadingGraficActiv) window.clearInterval(loadingGraficActiv);
353 loadingGraficActiv = window.setInterval("hideLoadingGraphic()", 8000);
354}
355
356function hideLoadingGraphic(){
357 window.clearInterval(loadingGraficActiv);
358 loadingGraficActiv = null;
359 if ($(".serverSettingsContentWrapper").hasClass("loading"))
360 $(".serverSettingsContentWrapper").removeClass("loading");
361}
362
363function onLanguageFetched(){
364 replaceStrings($(document));
365 $(window).trigger("hashchange");
366}
367
368function setVisitLinks() {
369 var id1 = document.getElementById("visitLink1");
370 var id2 = document.getElementById("visitLink2");
371 var id3 = document.getElementById("visitLink3");
372 var id4 = document.getElementById("visitLink4");
373 var id11 = document.getElementById("legalLink1");
374 if (noMediaFusionServer || myTwonkyDisabled) {
375 // Twonky
376 id1.innerHTML = "<a href='http://www.twonky.com/about.aspx' target='_blank' string='twonky.com'></a>";
377 // Mobile Apps
378 id2.innerHTML = "<a href='http://www.twonky.com/products/twonkymobile/default.aspx' target='_blank' string='mobileApps'></a>";
379 // Help center
380 id3.innerHTML = "<a href='http://community.twonky.com/twonky' target='_blank' string='helpCenter'></a>";
381 // -
382 id4.innerHTML = "";
383 // Licensing Information
384 id11.innerHTML = "<a href='' onclick='navigateTo(\"licenseinfo\"); return false;' string='licensinginfo'></a>";
385 } else {
386 // myTwonky
387 id1.innerHTML = "<a href='http://my.twonky.com' target='_blank' string='mytwonky.com'></a>";
388 // Twonky
389 id2.innerHTML = "<a href='http://www.twonky.com/about.aspx' target='_blank' string='twonky.com'></a>";
390 // Mobile Apps
391 id3.innerHTML = "<a href='http://www.twonky.com/products/twonkymobile/default.aspx' target='_blank' string='mobileApps'></a>";
392 // Help center
393 id4.innerHTML = "<a href='http://community.twonky.com/twonky' target='_blank' string='helpCenter'></a>";
394 // Licensing Information
395 id11.innerHTML = "<a href='' onclick='navigateTo(\"licenseinfo\"); return false;' string='licensinginfo'></a>";
396 }
397}
398
399//Initialize the Settings application by first reading the user's language setting, then loading the language file
400//and calling loadStatus.
401function initPage(){
402 if (top != self) {
403 // page is in an iFrame; show no header and footer
404 $("#headWrapper").hide();
405 $("#footer").hide();
406 }
407 // identify browser and OS
408 browserIdentification();
409 osIdentification();
410 // get the WebDav server url
411 makeGetRequest("/rpc/get_webdav_link", {}, function(response){
412 statusData["syncurl"] = response;
413 if (statusData["syncurl"].toLowerCase() == "nowebdav") noWebDavServer = true;
414 });
415 // get the portal links
416 makeGetRequest("/rpc/portal_page?login", {}, function(response){
417 statusData["portallogin"] = response;
418 });
419 makeGetRequest("/rpc/portal_page?register", {}, function(response){
420 statusData["portalregister"] = response;
421 });
422 makeGetRequest("/rpc/portal_page?login24", {}, function(response){
423 statusData["portallogin24"] = response + "/signin?pvx-orig-url=" + document.URL;
424 });
425 // connect media fusion server to update the server myTwonky status
426 initialPortalCheck();
427 $(".toggleButton").live("click", function(obj){
428 toggleContainer($(obj.currentTarget));
429 });
430 getConvertReadmeFile();
431 makeGetRequest("/rpc/get_all", {}, function(response){
432 parseSeparatedData(response, statusData, statusDataOrig, "=");
433 //Handle the version case separately.
434 //get_all, info_status and version return a "version" property.
435 //get_all.version is only set if the server comes with NMC
436 statusData["fullversion"] = statusData.version;
437 makeGetRequest("/webconfig/strings-en.json", {}, function(data){
438 defaultLanguageFile = parseJson(data);
439 languageFile = parseJson(data);
440 makeGetRequest("/webconfig/strings-"+statusData["language"]+".json", {}, function(data2){
441 languageFile = parseJson(data2);
442 onLanguageFetched();
443 });
444 });
445 var mediafusionServerUrl = statusData["mediafusionserverurl"];
446 // Setting empty value to 'mediafusionserverurl' is used to indicate
447 // that the MediaFusion is disabled, and must not be shown on the config page.
448 if (mediafusionServerUrl.length < 1) noMediaFusionServer = true;
449 if (statusData["disablemytwonky"] == 1) myTwonkyDisabled = true;
450 setVisitLinks(); // set links to myTwonky and twonky in footer
451 });
452}
453
454function onEventClick() {
455 if (!(changesMade || inputFieldClicked)) {
456 inputFieldClicked = true;
457 // give "save"-button a new look
458 $("#saveButton").addClass("confirm");
459 }
460}
461function onEventChange(){
462 if (!changesMade) {
463 changesMade = true;
464 // give "save"-button a new look
465 if (!$("#saveButton").hasClass("confirm")) $("#saveButton").addClass("confirm");
466 }
467}
468function resetChanged(){
469 inputFieldClicked = false;
470 changesMade = false;
471 // reset the look of the "save"-button
472 if ($("#saveButton").hasClass("confirm")) $("#saveButton").removeClass("confirm");
473}
474
475//If the user has changed any inputs on the page, display a dialog to warn them and prompt them to save changes.
476//Otherwise, navigate away.
477//navFunctionStr: A string indicating the function that should be called when navigation is performed
478//(e.g. "loadStatus()").
479function checkChanges(navFunctionStr){
480 if (changesMade) {
481 showDialogOverlay(function(){
482 return getString("saveprompt");
483 }, {}, {
484 1: {
485 text: getString("savechanges"),
486 onclick: saveHandler + " " + "hideDialogOverlay(); " + navFunctionStr
487 },
488 2: {
489 text: getString("discardchanges"),
490 onclick: "changesMade = false; hideDialogOverlay(); " + navFunctionStr
491 }
492 });
493 }
494 else {
495 eval(navFunctionStr);
496 }
497}
498
499function populateSettingsNav(){
500 if ($(".serverSettingsLeftNav").length == 0) {
501 makeGetRequest("/webconfig/settings-nav.htm", {}, function(response){
502 var responseHtml = $(response);
503 replaceStrings(responseHtml);
504 $("a", "#nav").removeClass("active");
505 $(".serverSettingsContentWrapper").removeClass("contentDisplay");
506 $("#leftNavContainer").html(responseHtml);
507 if (noWebDavServer) {
508 // remove nav-tree synchronization
509 $("#nav_synchronization").remove();
510 }
511 });
512 }
513}
514
515//Clear the selection on the currently selected left navigation item and highlight the new one. Cancel the
516//udpate timer if it exists.
517//currentNav: The newly clicked navigation item.
518function highlightNav(currentNav){
519 if (activeNav) {
520 activeNav.removeClass("current");
521 if (updateTimer) {
522 clearTimeout(updateTimer);
523 }
524 }
525 currentNav.addClass("current");
526 activeNav = currentNav;
527}
528
529function refreshPortalInfo() {
530 var c = document.cookie;
531 var a = c.split(";");
532 for (var j=0;j<a.length;j++) {
533 var cookieID = a[j].substring(0,a[j].indexOf("="));
534 var cookieValue = a[j].substring(a[j].indexOf("=")+1, a[j].length);
535 if (cookieID.indexOf("login24") >= 0) {
536 if (cookieValue.indexOf("true") >= 0) {
537 document.cookie = "login24=false;";
538 startPortalCheckTimer();
539 }
540 }
541 }
542}
543
544// accountingstatus:
545//#define ACCOUNTING_TRIAL_VERSION 1 "TwonkyServer"
546//#define ACCOUNTING_REGISTERED_VERSION_TS_ONLY 2
547//#define ACCOUNTING_REGISTERED_VERSION_TSTML 3
548//#define ACCOUNTING_REGISTERED_VERSION_TSTMF 4
549//#define ACCOUNTING_PORTAL_VERSION 5 "TwonkyServer Free"
550//#define ACCOUNTING_PREMIUM_VERSION 6 "TwonkyServer Premium"
551//#define ACCOUNTING_OEM_VERSION 7
552function getServerType(accStatus) {
553 var str = "TwonkyServer";
554 switch (accStatus) {
555 case "2": str = "TwonkyServer";
556 break;
557 case "5": str = "TwonkyServer free";
558 break;
559 case "6": str = "TwonkyServer Premium";
560 break;
561 }
562 return str;
563}
564
565// ------------------------
566// status page
567// ------------------------
568//Load data for the Status page.
569//isInitial: Default false. /rpc/get_all is called on application load, so set isInitial to true to avoid a duplicate
570//call.
571function loadStatus(isInitial){
572 returnedCalls = 0;
573 expectedCalls = (isInitial) ? 8 : 10;
574 saveHandler = "function(){};"
575 inputFieldClicked = false;
576 changesMade = false;
577
578 showLoadingGraphic();
579 refreshPortalInfo(); // check the portal status after a login from a free server after 24 hours
580
581 if (!isInitial) {
582 makeGetRequest("/rpc/get_all", {}, function(response){
583 parseSeparatedData(response, statusData, statusDataOrig, "=");
584 statusData["fullversion"] = statusData.version; // info_status returns the property "version" too
585 returnedCalls++;
586 if (expectedCalls == returnedCalls) {
587 loadStatusHtml();
588 hideLoadingGraphic();
589 }
590 });
591 makeGetRequest("/rpc/get_webdav_link", {}, function(response){
592 statusData["syncurl"] = response;
593 returnedCalls++;
594 if (statusData["syncurl"].toLowerCase() == "nowebdav") noWebDavServer = true;
595 if (expectedCalls == returnedCalls) {
596 loadStatusHtml();
597 hideLoadingGraphic();
598 }
599 });
600 }
601
602 makeGetRequest("/rpc/info_status", {}, function(response){
603 parseSeparatedData(response, statusData, statusDataOrig, "|");
604 statusData["serverversion"] = statusData.version; // get_all returns the property "version" too
605 // statusData["servertype"] = getServerType(statusData["licensestatus"]);
606 // statusData["servertypepart2"] = statusData["servertype"];
607 returnedCalls++;
608 if (expectedCalls == returnedCalls) {
609 loadStatusHtml();
610 hideLoadingGraphic();
611 }
612 });
613 // function get_server_type restored - should be retired again. See also chapter advanced!
614 makeGetRequest("/rpc/get_server_type", {}, function(response){
615 statusData["servertype"] = response;
616 statusData["servertypepart2"] = response;
617 advanced["servertype"] = response;
618 returnedCalls++;
619 if (expectedCalls == returnedCalls) {
620 loadStatusHtml();
621 hideLoadingGraphic();
622 }
623 });
624
625 makeGetRequest("/rpc/get_friendlyname", {}, function(response){
626 statusData["friendlynamestring"] = response;
627 returnedCalls++;
628 if (expectedCalls == returnedCalls) {
629 loadStatusHtml();
630 hideLoadingGraphic();
631 }
632 });
633
634 makeGetRequest("/rpc/info_nics", {}, function(response){
635 parseData(response, statusData, statusDataOrig, "nics", "\n");
636 returnedCalls++;
637 if (expectedCalls == returnedCalls) {
638 loadStatusHtml();
639 hideLoadingGraphic();
640 }
641 });
642
643 makeGetRequest("/rpc/stream_active", {}, function(response){
644 parseData(response, statusData, statusDataOrig, "streams", "SA:");
645 returnedCalls++;
646 if (expectedCalls == returnedCalls) {
647 loadStatusHtml();
648 hideLoadingGraphic();
649 }
650 });
651
652 makeGetRequest("/rpc/get_portal_info", {}, function(response){
653 statusData["portalinfo"] = response;
654 returnedCalls++;
655 if (expectedCalls == returnedCalls) {
656 loadStatusHtml();
657 hideLoadingGraphic();
658 }
659 });
660
661 makeGetRequest("/rpc/get_option?portalusername", {}, function(response){
662 statusData["portalusername"] = response;
663 returnedCalls++;
664 if (expectedCalls == returnedCalls) {
665 loadStatusHtml();
666 hideLoadingGraphic();
667 }
668 });
669
670
671 $.ajax({
672 url: "/rpc/get_timeout_period",
673 success: function(response){
674 returnedCalls++;
675 statusData["lastlogin"] = response;
676 if (expectedCalls == returnedCalls) {
677 loadStatusHtml();
678 hideLoadingGraphic();
679 }
680 },
681 error: function(response){
682 returnedCalls++;
683 if (expectedCalls == returnedCalls) {
684 loadStatusHtml();
685 hideLoadingGraphic();
686 }
687 }
688 });
689}
690
691function handleStatusData(element, key, data){
692 var returnValue = "";
693 switch (key) {
694 case "restartpending":
695 returnValue = (data == 0) ? (getString("no")) : (getString("yes"));
696 break;
697 case "wmdrmstatus":
698 returnValue = (data) ? (data.toUpperCase()) : ("");
699 break;
700 case "uptime":
701 var days = data[0];
702 var timePieces = data[1].split(":");
703 returnValue = days + " " + getString("days") + ", " + timePieces[0] + " " + getString("hours") + ", " + timePieces[1] + " " + getString("minutes") + ", " + timePieces[2] + " " + getString("seconds");
704 break;
705 case "syncurl":
706 returnValue = (data.toLowerCase() == "nowebdav") ? ("-") : (data);
707 break;
708 case "nics":
709 if (data) {
710 $.each(data, function(i, value){
711 if (value.length > 0 && value.lastIndexOf("127.0.0.1") == -1) {
712 var nicPieces = value.split(",");
713 var mac = (nicPieces[1]) ? (nicPieces[1]) : ("")
714 returnValue += "<span class='nicIp'>" + nicPieces[0] + "</span>" + " " + mac + "<br />";
715 }
716 });
717 }
718 break;
719 case "cdkey":
720 if (statusData["licensestatus"] == "4") break; // OEM-version: do not show the key
721 if (data && statusData["servertype"].toLowerCase().indexOf("free") == -1) {
722 returnValue = '<div class="serverStatusLabel floatL">' + getString("cdkey") + '</div><div class="floatL">' + data + '</div><div class="clear"></div>';
723 }
724 break;
725 case "licensestatus":
726 //if ((!statusData["cdkey"] || data < 1) && statusData["servertype"].toLowerCase().indexOf("free") == -1) {
727 if (data < 2) {
728 //Remove the element's key to prevent it from being overwritten during the automatic update timer.
729 element.attr("key", "nothing");
730 returnValue += '<div class="boxHeader">\
731 <span class="titleWrapper">\
732 <span class="title">' + getString("licensekey") + '</span>\
733 </span>\
734 <div class="clear" />\
735 </div>\
736 <div><div>' +
737 getString("licensekeycaption") +
738 '</div><br />';
739 if (os != "Mac") {
740 // Windows, Linux: show 8 fields for license key
741 for (var i = 0; i < 8; i++) {
742 returnValue += '<input type="text" class="licenseKeyInput floatL" maxchars="4" onchange="onLicenseInput($(this))" onkeyup="onLicenseInput($(this)); onLicenseInputKeyUp(event)"></input>'
743 }
744 } else {
745 // Mac: show one field for license key
746 returnValue += '<input type="text" class="licenseKeyInputMac floatL" maxchars="39" onkeyup="onLicenseInputKeyUp(event)"></input>'
747 }
748
749 returnValue += '<a class="actionbtn floatL" onclick="saveLicenseKey()" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)">\
750 <span class="actionbtn_l"></span>\
751 <span class="actionbtn_c">' + getString("enter") + '</span>\
752 <span class="actionbtn_r"></span>\
753 </a>\
754 <div class="clear"></div>'
755
756 switch (data) {
757 case 1:
758 returnValue += "<div class='error'>" + statusData["licensedays"] + " " + getString("daysremaining") + "</div>"
759 break;
760 case 2:
761 case 3:
762 case 4:
763 break;
764 default:
765 returnValue += "<div class='error'>" + getString("license" + data) + "</div>"
766 break;
767 }
768
769 returnValue += '</div><div class="serverContentSpacer"></div>';
770 }
771 break;
772 case "streams":
773 returnValue = (data[1] > 0) ? (getString("activestreams")) : (getString("noactivestreams"));
774 break;
775 case "servertype":
776 // show the server type and version
777 // free: Twonky 7.0 (servertype, fullversion)
778 // premium: TwonkyServer Premium 7.0 (servertype, fullversion)
779 // standard: Twonky 7.0 Special (servertype, fullversion, servertypepart2)
780 var serverType = data.toLowerCase();
781 if (serverType.lastIndexOf("premium") > -1) {
782 returnValue = getString("premiumserver");
783 }
784 else
785 if (serverType.lastIndexOf("free") > -1) {
786 returnValue = getString("freeserver");
787 }
788 else {
789 returnValue = getString("twonkyserver");
790 }
791 break;
792 case "servertypepart2":
793 var serverType = data.toLowerCase();
794 if ((serverType.lastIndexOf("premium") == -1) && (serverType.lastIndexOf("free") == -1)) {
795 returnValue = getString("twonkyservertextpart2");
796 }
797 else return "";
798 break;
799 case "portalinfo":
800 var serverType = statusData["servertype"];
801 if (noMediaFusionServer || myTwonkyDisabled) break;
802 switch (data) {
803 case "notregistered":
804 if (serverType.toLowerCase().lastIndexOf("free") > -1) {
805 if (browser == "other") {
806 returnValue = '<div>' +
807 getString("registrationrequiredcaption") +
808 '</div>\
809 <div class="smallServerContentSpacer"></div>\
810 <div><a class="actionbtnmd bold" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)" onclick="openPortalLink(\'register\')"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
811 getString("registerserver") +
812 '</span><span class="actionbtn_r"></span></a></div>\
813 <div class="serverContentSpacer"></div>';
814 } else {
815 // Safari browser
816 returnValue = '<div>' +
817 getString("registrationrequiredcaption") +
818 '</div>\
819 <div class="smallServerContentSpacer"></div>\
820 <div><a class="actionbtnmd bold" href="' + statusData["portalregister"] + '" target="_blank" onclick="javascript:startPortalCheckTimerOnClick(\'register\')"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
821 getString("registerserver") +
822 '</span><span class="actionbtn_r"></span></a></div>\
823 <div class="serverContentSpacer"></div>';
824 }
825 }
826 else {
827 if (browser == "other") {
828 returnValue = '<div>' +
829 getString("registrationoptionalcaption") +
830 '</div>\
831 <div class="smallServerContentSpacer"></div>\
832 <div><a class="actionbtnmd bold" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)" onclick="openPortalLink(\'register\')"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
833 getString("registerserver") +
834 '</span><span class="actionbtn_r"></span></a></div>\
835 <div class="serverContentSpacer"></div>';
836 } else {
837 // Safari browser
838 returnValue = '<div>' +
839 getString("registrationoptionalcaption") +
840 '</div>\
841 <div class="smallServerContentSpacer"></div>\
842 <div><a class="actionbtnmd bold" href="' + statusData["portalregister"] + '" target="_blank" onclick="javascript:startPortalCheckTimerOnClick(\'register\')"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
843 getString("registerserver") +
844 '</span><span class="actionbtn_r"></span></a></div>\
845 <div class="serverContentSpacer"></div>';
846 }
847 }
848 break;
849 case "notloggedin":
850 if (browser == "other") {
851 returnValue = '<div>' +
852 getString("loginneededcaption") +
853 '</div>\
854 <div class="smallServerContentSpacer"></div>\
855 <div><a class="actionbtnmd bold" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)" onclick="openPortalLink(\'login\')"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
856 getString("login") +
857 '</span><span class="actionbtn_r"></span></a></div>\
858 <div class="serverContentSpacer"></div>';
859 } else {
860 // Safari browser
861 returnValue = '<div>' +
862 getString("loginneededcaption") +
863 '</div>\
864 <div class="smallServerContentSpacer"></div>\
865 <div><a class="actionbtnmd bold" href="' + statusData["portallogin"] + '" target="_blank" onclick="javascript:startPortalCheckTimerOnClick(\'login\')"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
866 getString("login") +
867 '</span><span class="actionbtn_r"></span></a></div>\
868 <div class="serverContentSpacer"></div>';
869 }
870 break;
871 case "portaldisabled":
872 returnValue = getString("portaldisabled") + '<div class="serverContentSpacer"></div>';
873 break;
874 case "useronline":
875 var renewStr = "";
876 if (serverType.toLowerCase().lastIndexOf("free") > -1) {
877 serverType = "free";
878 if (statusData["lastlogin"].indexOf(":") > 0) {
879 // format hours:minutes
880 var timePieces = statusData["lastlogin"].split(":");
881 var hours = timePieces[0];
882 var minutes = timePieces[1];
883 var timeoutStr = getString("renewlicense");
884 renewStr = timeoutStr.replace("{0}", hours).replace("{1}", minutes);
885 if (browser == "other") {
886 renewStr = renewStr.replace("myTwonky.com", "<a class=\'inlineLink\' href=\"javascript:openPortalLink(\'login\')\">myTwonky.com</a>");
887 } else {
888 // Safari browser
889 renewStr = renewStr.replace("myTwonky.com", "<a class=\'inlineLink\' href=\'" + statusData["portallogin"] + "\' target=\'_blank\' onclick=\'javascript:startPortalCheckTimerOnClick(\"register\")\'>myTwonky.com</a>");
890 }
891 } else {
892 // in seconds
893 var seconds1 = statusData["lastlogin"];
894 if (seconds1 > 0) {
895 var minutes1 = (seconds1 - (seconds1 % 60)) / 60;
896 var minutes = minutes1 % 60;
897 var hours = (minutes1 - minutes) / 60;
898 } else {
899 var hours = "0";
900 var minutes = "00";
901 }
902 if (hours > 23) {
903 // show remaining days
904 var days = (hours - (hours % 24)) / 24;
905 var timeoutStr = getString("renewlicenseindays");
906 renewStr = timeoutStr.replace("{0}", days);
907 if (browser == "other") {
908 renewStr = renewStr.replace("myTwonky.com", "<a class=\'inlineLink\' href=\"javascript:openPortalLink(\'login\')\">myTwonky.com</a>");
909 } else {
910 // Safari browser
911 renewStr = renewStr.replace("myTwonky.com", "<a class=\'inlineLink\' href=\'" + statusData["portallogin"] + "\' target=\'_blank\' onclick=\'javascript:startPortalCheckTimerOnClick(\"register\")\'>myTwonky.com</a>");
912 }
913 } else {
914 // show remaining hours and minutes
915 var timeoutStr = getString("renewlicense");
916 renewStr = timeoutStr.replace("{0}", hours).replace("{1}", minutes);
917 if (browser == "other") {
918 renewStr = renewStr.replace("myTwonky.com", "<a class=\'inlineLink\' href=\"javascript:openPortalLink(\'login\')\">myTwonky.com</a>");
919 } else {
920 // Safari browser
921 renewStr = renewStr.replace("myTwonky.com", "<a class=\'inlineLink\' href=\'" + statusData["portallogin"] + "\' target=\'_blank\' onclick=\'javascript:startPortalCheckTimerOnClick(\"register\")\'>myTwonky.com</a>");
922 }
923 }
924 }
925 }
926 returnValue = getString("loggedinas") + " " + statusData["portalusername"] + '<br /><div>' + renewStr + '<div class="serverContentSpacer"></div>';
927 break;
928 case "connectionissue":
929 returnValue = getString("connectionissue") + '<div class="serverContentSpacer"></div>';
930 break;
931 case "connecting":
932 returnValue = getString("portalconnecting") + '<div class="serverContentSpacer"></div>';
933 break;
934 }
935 break;
936 default:
937 returnValue = data;
938 break;
939 }
940 element.html(returnValue);
941}
942
943function onLicenseInput(input){
944 if (input.val().match(/^[A-Z0-9]{4}(-[A-Z0-9]{4}){7}$/)) {
945 var keyPieces = input.val().split("-");
946 var inputs = $(".licenseKeyInput");
947 $.each(inputs, function(i, element){
948 $(element).val(keyPieces[i]);
949 });
950 }
951 else {
952 if (input.val().length > input.attr("maxchars")) {
953 input.val(input.val().substring(0, input.attr("maxchars")));
954 }
955 if (input.val().length == input.attr("maxchars")) {
956 input.next().focus();
957 }
958 }
959}
960
961function onLicenseInputKeyUp(event){
962 if (event.which == 13) {
963 saveLicenseKey();
964 }
965}
966
967function saveLicenseKey(){
968 var key = "";
969 resetChanged();
970 if (os != "Mac") {
971 var inputs = $(".licenseKeyInput");
972 $.each(inputs, function(i, element){
973 key += $(element).val();
974 if (i != inputs.length - 1) {
975 key += "-";
976 }
977 });
978 } else key = $(".licenseKeyInputMac")[0].value;
979 var data = "cdkey=" + key + "\n";
980 makeGetRequest("/rpc/set_option?"+data, {}, function(response) {});
981 //location.reload();
982 var timerKey = setTimeout("reloadSide()",30);
983}
984function reloadSide() {
985 location.reload();
986}
987
988function initialPortalCheck() {
989 // twonky server connect to media fusion server to update the portal data
990 makeGetRequest("/rpc/get_portal_info?onlineStatus", {}, function(response) {});
991}
992function openPortalLink(arg){
993 // login with redirect to config pages if 24 hours are over - only free server
994 if (arg == "login24") {
995 // call the portal with the current url (server config page) as parameter
996 // example call: window.open("http://staging-portal.twonky.com/signin?pvx-orig-url=http://127.0.0.1:9000", ....)
997 // set cookie. At restart update server portal data.
998 document.cookie = "login24=true;";
999 makeGetRequest("/rpc/portal_page?" + arg, {}, function(response){
1000 window.open(response+"/signin?pvx-orig-url="+document.URL, "_self", 'menubar=yes,scrollbars=yes,status=yes,toolbar=yes,resizable=yes');
1001 });
1002 }
1003 if (arg == "register") {
1004 makeGetRequest("/rpc/portal_page?" + arg, {}, function(response){
1005 window.open(response, "PortalPage", 'menubar=yes,scrollbars=yes,status=yes,toolbar=yes,resizable=yes');
1006 });
1007 }
1008 if (arg == "login") {
1009 makeGetRequest("/rpc/portal_page?" + arg, {}, function(response){
1010 window.open(response, "PortalPage", 'menubar=yes,scrollbars=yes,status=yes,toolbar=yes,resizable=yes');
1011 });
1012 }
1013 startPortalCheckTimer();
1014}
1015function startPortalCheckTimer() {
1016 if (portalTimerIntervalCount == 0) {
1017 // start a timer to update the server portal data
1018 portalTimerIntervalCount = 1;
1019 portalTimerInterval = portalTimerInterval1;
1020 portalTimer = setInterval(checkPortalStatus, portalTimerInterval1);
1021 }
1022}
1023function startPortalCheckTimerOnClick(arg) {
1024 if (arg == "login24") document.cookie = "login24=true;";
1025 startPortalCheckTimer();
1026 return true;
1027}
1028function disassociatePortalLink(servertype) {
1029 if (servertype == "free") var msg = getString("dialog_portaldisassociatefree");
1030 else var msg = getString("dialog_portaldisassociatepremium");
1031 confirmDialog(msg,"confirmDisassociatePortalLink()");
1032}
1033function confirmDisassociatePortalLink() {
1034 hideDialogOverlay();
1035 makeGetRequest("/rpc/portal_disassociate", {}, function(response){});
1036 $("#portaldisassociation").hide();
1037}
1038function checkPortalStatus() {
1039 // twonky server calls media fusion server
1040 makeGetRequest("/rpc/get_portal_info?onlineStatus", {}, function(response){
1041 if (response == "online") {
1042 portalTimerInterval = portalTimerInterval3;
1043 portalTimerIntervalCount = portalTimerInterval3Count;
1044 };
1045 });
1046 // update status page
1047 updateStatus();
1048 // increase interval counter
1049 portalTimerIntervalCount ++;
1050 if (portalTimerInterval == portalTimerInterval1) {
1051 if (portalTimerIntervalCount > portalTimerInterval1Count) {
1052 // stop timer and start timer with interval 2
1053 clearInterval(portalTimer);
1054 portalTimerIntervalCount = 1;
1055 portalTimerInterval = portalTimerInterval2;
1056 portalTimer = setInterval(checkPortalStatus, portalTimerInterval2);
1057 }
1058 }
1059 if (portalTimerInterval == portalTimerInterval2) {
1060 if (portalTimerIntervalCount > portalTimerInterval2Count) {
1061 // stop timer and start timer with interval 3
1062 clearInterval(portalTimer);
1063 portalTimerIntervalCount = 1;
1064 portalTimerInterval = portalTimerInterval3;
1065 portalTimer = setInterval(checkPortalStatus, portalTimerInterval3);
1066 }
1067 }
1068 if (portalTimerInterval == portalTimerInterval3) {
1069 if (portalTimerIntervalCount > portalTimerInterval3Count) {
1070 // stop portal timer
1071 clearInterval(portalTimer);
1072 portalTimerIntervalCount = 0;
1073 }
1074 }
1075}
1076
1077function loadStatusHtml(){
1078 makeGetRequest("/webconfig/status.htm", {}, function(response){
1079 var responseHtml = $(response);
1080 replaceStrings(responseHtml);
1081 replaceData(responseHtml, statusData, handleStatusData);
1082 showToggleButtons(responseHtml);
1083 $(".serverSettingsContentWrapper").html(responseHtml);
1084 if (noMediaFusionServer || myTwonkyDisabled) {
1085 $("#portalHeader").addClass("hideSubheader");
1086 }
1087 if (noWebDavServer) {
1088 $("#statusSyncUrl").hide();
1089 }
1090 highlightNav($("#nav_status"));
1091 hideLoadingGraphic();
1092 if (statusData["portalinfo"] == "connecting")
1093 updateTimer = setInterval(updateStatusTimerCheck, updateTimerInterval1);
1094 else
1095 updateTimer = setInterval(updateStatus, updateTimerInterval);
1096 });
1097}
1098
1099// special action for status update
1100// The portal info is updated every 2 seconds.
1101// If the portal is "online" the page is updated immediatly and the update timer is set to 30 seconds.
1102// The page is updated every 10 seconds three times (updateTimerInterval1),
1103// later every 30 seconds (updateTimerInterval)
1104function updateStatusTimerCheck() {
1105 makeGetRequest("/rpc/get_portal_info", {}, function(response){
1106 statusData["portalinfo"] = response;
1107 updateTimerInterval1Count++;
1108 if (statusData["portalinfo"] != "connecting") {
1109 clearInterval(updateTimer);
1110 updateTimer = setInterval(updateStatus, updateTimerInterval);
1111 updateStatus();
1112 }
1113 if (updateTimerInterval1Count >= 30) {
1114 clearInterval(updateTimer);
1115 updateTimer = setInterval(updateStatus, updateTimerInterval);
1116 }
1117 });
1118}
1119
1120function updateStatus(){
1121 returnedCalls = 0;
1122 expectedCalls = 5;
1123 showLoadingGraphic();
1124
1125 makeGetRequest("/rpc/info_status", {}, function(response){
1126 parseSeparatedData(response, statusData, statusDataOrig, "|");
1127 statusData["serverversion"] = statusData.version; // get_all returns the property "version" too
1128 returnedCalls++;
1129 if (expectedCalls == returnedCalls) {
1130 replaceData($(".serverSettingsContentWrapper"), statusData, handleStatusData);
1131 hideLoadingGraphic();
1132 }
1133 });
1134
1135 makeGetRequest("/rpc/get_all", {}, function(response){
1136 parseSeparatedData(response, statusData, statusDataOrig, "=");
1137 statusData["fullversion"] = statusData.version; // info_status returns the property "version" too
1138 returnedCalls++;
1139 if (expectedCalls == returnedCalls) {
1140 replaceData($(".serverSettingsContentWrapper"), statusData, handleStatusData);
1141 hideLoadingGraphic();
1142 }
1143 });
1144
1145 makeGetRequest("/rpc/get_portal_info", {}, function(response){
1146 statusData["portalinfo"] = response;
1147 returnedCalls++;
1148 if (expectedCalls == returnedCalls) {
1149 replaceData($(".serverSettingsContentWrapper"), statusData, handleStatusData);
1150 hideLoadingGraphic();
1151 }
1152 });
1153
1154 makeGetRequest("/rpc/get_option?portalusername", {}, function(response){
1155 statusData["portalusername"] = response;
1156 returnedCalls++;
1157 if (expectedCalls == returnedCalls) {
1158 replaceData($(".serverSettingsContentWrapper"), statusData, handleStatusData);
1159 hideLoadingGraphic();
1160 }
1161 });
1162
1163 $.ajax({
1164 url: "/rpc/get_timeout_period",
1165 success: function(response){
1166 returnedCalls++;
1167 statusData["lastlogin"] = response;
1168 if (expectedCalls == returnedCalls) {
1169 replaceData($(".serverSettingsContentWrapper"), statusData, handleStatusData);
1170 hideLoadingGraphic();
1171 }
1172 },
1173 error: function(response){
1174 returnedCalls++;
1175 if (expectedCalls == returnedCalls) {
1176 replaceData($(".serverSettingsContentWrapper"), statusData, handleStatusData);
1177 hideLoadingGraphic();
1178 }
1179 }
1180 });
1181}
1182
1183
1184// ------------------------
1185// setup page
1186// ------------------------
1187function loadSetup(){
1188 returnedCalls = 0;
1189 expectedCalls = 2;
1190 saveHandler = "submitSetupData();"
1191 inputFieldClicked = false;
1192 changesMade = false;
1193
1194 showLoadingGraphic();
1195
1196 makeGetRequest("/rpc/get_all", {}, function(response){
1197 parseSeparatedData(response, setup, setupOrig, "=");
1198 returnedCalls++;
1199 if (expectedCalls == returnedCalls) {
1200 loadSetupHtml();
1201 hideLoadingGraphic();
1202 }
1203 });
1204
1205 makeGetRequest("/rpc/view_names", {}, function(response){
1206 parseData(response, setup, setupOrig, "viewnames", ",");
1207 returnedCalls++;
1208 if (expectedCalls == returnedCalls) {
1209 loadSetupHtml();
1210 hideLoadingGraphic();
1211 }
1212 });
1213}
1214
1215function loadSetupHtml(){
1216 makeGetRequest("/webconfig/setup.htm", {}, function(response){
1217 var responseHtml = $(response);
1218 replaceStrings(responseHtml);
1219 replaceData(responseHtml, setup, handleSetupData);
1220 showToggleButtons(responseHtml);
1221
1222 $(".serverSettingsContentWrapper").html(responseHtml);
1223 $("input", "#setupContainer").live("click", onEventClick);
1224 $("input,select", "#setupContainer").live("change", onEventChange);
1225 highlightNav($("#nav_setup"));
1226 });
1227}
1228
1229function handleSetupData(element, key, data){
1230 switch (key) {
1231 case "friendlyname":
1232 element.val(data);
1233 break;
1234 case "language":
1235 var matchingLanguage = $("[value=" + data + "]", element);
1236 matchingLanguage.attr("selected", "yes");
1237 break;
1238 case "viewnames":
1239 html = "";
1240 $.each(data, function(i, value){
1241 var selected = "";
1242 if (setup["defaultview"] == value) {
1243 selected = "checked";
1244 selectedNavType = value;
1245 }
1246 html += '<div class="radioControlWrapper">\
1247 <input name="viewname" id="' + value + '" ' + selected + ' type="radio" onclick="setNavType(\'' + value + '\')" />\
1248 <div class="radioControlTextWrapper">\
1249 <div class="radioHeader">\
1250 ' +
1251 getString(value) +
1252 '\
1253 </div>\
1254 <div class="smallFont">\
1255 ' +
1256 getString(value + "caption") +
1257 '\
1258 </div>\
1259 </div>\
1260 <div class="clear">\
1261 </div>\
1262 </div>';
1263 });
1264 element.html(html);
1265 break;
1266 }
1267}
1268
1269function setNavType(navType){
1270 selectedNavType = navType;
1271}
1272
1273function submitSetupData(){
1274 returnedCalls = 0;
1275 expectedCalls = 1;
1276 hideActionButtons();
1277 resetChanged();
1278 var data = "";
1279 if (setupOrig["friendlyname"] != $("#servername").val()) data += "friendlyname=" + $("#servername").val() + "\n";
1280 var newLanguage = $("#language").val();
1281 if (setupOrig["language"] != newLanguage) data += "language=" + newLanguage + "\n";
1282 if (setupOrig["defaultview"] != selectedNavType) data += "defaultview=" + selectedNavType + "\n";
1283 //var data = "friendlyname=" + $("#servername").val() + "\nlanguage=" + $("#language").val() + "\ndefaultview=" + selectedNavType + "\n";
1284 makePostRequest("/rpc/set_all", {}, data, function(){
1285 returnedCalls++;
1286 if (setupOrig["language"] != newLanguage) {
1287 setWebDavLanguage($("#language").val()); // set also the WebDav server language
1288 setReloadSide = true;
1289 }
1290 finishSaving();
1291 });
1292}
1293
1294function setWebDavLanguage(lang) {
1295 var request = loadXMLDoc("/set_language?lang="+lang,"");
1296}
1297
1298// ------------------------
1299// sharing page
1300// ------------------------
1301function loadSharing(){
1302 returnedCalls = 0;
1303 expectedCalls = 3;
1304 saveHandler = "submitSharingData();"
1305 inputFieldClicked = false;
1306 changesMade = false;
1307
1308 showLoadingGraphic();
1309
1310 makeGetRequest("/rpc/get_all", {}, function(response){
1311 parseSeparatedData(response, sharing, sharingOrig, "=");
1312 returnedCalls++;
1313 if (expectedCalls == returnedCalls) {
1314 loadSharingHtml();
1315 hideLoadingGraphic();
1316 }
1317 });
1318
1319 makeGetRequest("/rpc/info_clients", {}, function(response){
1320 parseData(response, sharing, sharingOrig, "clients", "\n");
1321 returnedCalls++;
1322 if (expectedCalls == returnedCalls) {
1323 loadSharingHtml();
1324 hideLoadingGraphic();
1325 }
1326 });
1327
1328 makeGetRequest("/rpc/info_connected_clients", {}, function(response){
1329 parseData(response, sharing, sharingOrig, "mediareceivers", "##########\n");
1330 returnedCalls++;
1331 if (expectedCalls == returnedCalls) {
1332 loadSharingHtml();
1333 hideLoadingGraphic();
1334 }
1335 });
1336}
1337
1338function loadSharingHtml(){
1339 makeGetRequest("/webconfig/sharing.htm", {}, function(response){
1340 var responseHtml = $(response);
1341 replaceStrings(responseHtml);
1342 replaceData(responseHtml, sharing, handleSharingData);
1343 showToggleButtons(responseHtml);
1344
1345 $(".serverSettingsContentWrapper").html(responseHtml);
1346 $("input", "#sharingContainer").live("click", onEventClick);
1347 $("input,select", "#sharingContainer").live("change", onEventChange);
1348 highlightNav($("#nav_sharing"));
1349 updateSharing();
1350 updateTimer = setInterval(updateSharing, updateTimerInterval);
1351 });
1352}
1353
1354function updateSharing(){
1355 makeGetRequest("/rpc/info_connected_clients", {}, function(response){
1356 parseData(response, sharing, "mediareceivers", "##########\n");
1357 updateMediaReceivers($("[key=mediareceivers]"), sharing.mediareceivers)
1358 });
1359}
1360
1361function splitContentDirs(data){
1362 var delimiter = ",[-\\+\\*][AMVPDamvpd]\\|";
1363 var contentDirs = new Array();
1364 var index;
1365 while ((index = data.search(delimiter)) != -1) {
1366 contentDirs.push(data.substring(0, index));
1367 data = data.substring(index + 1);
1368 }
1369 contentDirs.push(data);
1370 return contentDirs;
1371}
1372
1373function handleSharingData(element, key, data){
1374 switch (key) {
1375 case "rmautoshare":
1376 if (sharing["platform"] == "WIN32") {
1377 element.show();
1378 if (data > 0) {
1379 $("input[type=checkbox]", element).attr("checked", "true");
1380 }
1381 }
1382 break;
1383 case "contentdir":
1384 folderBrowseDialogMsg = 1;
1385 updateSharedFolderList(element, data);
1386 break;
1387 case "v":
1388 case "clientautoenable":
1389 element.attr("checked", data > 0)
1390 break;
1391 case "mediareceivers":
1392 updateMediaReceivers(element, data);
1393 break;
1394 }
1395}
1396
1397//A utility function used during dynamic HTML generation to determine whether a dropdown option should be selected.
1398//mediaKey: The value of the input in question.
1399//compareKey: The string to compare to.
1400function checkSelectedMediaOption(mediaKey, compareKey){
1401 return (mediaKey == compareKey) ? ("selected") : ("");
1402}
1403
1404function checkShareBox(checkbox){
1405 if (checkbox.is(":checked")) {
1406 var shareBox = $(".sharedCheckbox", checkbox.parent());
1407 shareBox.attr("checked", true);
1408 }
1409}
1410
1411function uncheckAggregationBox(checkbox){
1412 if (!checkbox.is(":checked")) {
1413 var aggregationBox = $(".aggregationCheckbox", checkbox.parent());
1414 aggregationBox.attr("checked", false);
1415 }
1416}
1417
1418//Display a dialog that allows the user to browse folders on his local machine. This can't be a browser control
1419//because browsers only allow file selection, not folder browsing.
1420//rowNumber: The number of the selected folder browse row. Used to track which input the user is working with.
1421function showFolderBrowse(rowNumber){
1422 showDialogOverlay(createFolderBrowseDialog, {
1423 onstart: makeGetRequest("/rpc/dir", {
1424 "path": ""
1425 }, function(response){
1426 populateDirs(response, "", "");
1427 })
1428 }, {
1429 1: {
1430 text: getString("select"),
1431 onclick: "selectDir('" + rowNumber + "')"
1432 },
1433 2: {
1434 text: getString("cancel"),
1435 onclick: "hideDialogOverlay()"
1436 }
1437 }, "folderBrowse");
1438}
1439
1440//Hide the folder selection dialog and populate an input with the user's selected directory.
1441//rowNumber: The number of the selected folder browse row. Used to track which input the user is working with.
1442function selectDir(rowNumber){
1443 if ($("#dirPathDisplay").html()) {
1444 $("#pathInput" + rowNumber).val($("#dirPathDisplay").html());
1445 onEventChange();
1446 hideDialogOverlay();
1447 }
1448}
1449
1450function createFolderBrowseDialog(){
1451 if (folderBrowseDialogMsg == 1)
1452 return '<div>\
1453 <div class="boxHeader">\
1454 <span class="titleWrapper">\
1455 <span class="title">' + getString("selectfolder") + '</span>\
1456 </span>\
1457 <div class="clear" />\
1458 </div>\
1459 <div id="dirPathDisplay" class="dirPathDisplay"></div>\
1460 <div id="dirDisplayContainer"></div>\
1461 </div>';
1462 else
1463 return '<div>\
1464 <div class="boxHeader">\
1465 <span class="titleWrapper">\
1466 <span class="title">' + getString("selectsyncfolder") + '</span>\
1467 </span>\
1468 <div class="clear" />\
1469 </div>\
1470 <div id="dirPathDisplay" class="dirPathDisplay"></div>\
1471 <div id="dirDisplayContainer"></div>\
1472 </div>';
1473}
1474
1475//The maximum height of the directory display area before it begins scrolling, in pixels.
1476function getWindowHeight() {
1477 var windowHeight = 50;
1478 if (typeof( window.innerWidth ) == 'number' ) {
1479 //Non-IE
1480 windowHeight = window.innerHeight;
1481 } else if (document.documentElement && document.documentElement.clientHeight) {
1482 //IE 6+ in 'standards compliant mode'
1483 windowHeight = document.documentElement.clientHeight;
1484 } else if (document.body && document.body.clientHeight) {
1485 //IE 4 compatible
1486 windowHeight = document.body.clientHeight;
1487 }
1488 return windowHeight;
1489}
1490
1491//Generate HTML to display the list of directories, along with a breadcrumb and a link for the parent directory.
1492//response: The data containing the directory list.
1493//rootPath: The path of the previous directory.
1494//rootId: The id of the previous directory.
1495function populateDirs(response, rootPath, rootId){
1496 ($("#dirDisplayContainer")).removeClass("scroll");
1497 $("#dirDisplayContainer").css("height", "auto");
1498 var html = "";
1499 var responsePieces = response.split("\n");
1500 var platformSpecificSeparator = responsePieces[0];
1501 var dirDisplay = $("#dirPathDisplay");
1502 dirDisplay.attr("dirid", rootId);
1503 dirDisplay.html(rootPath);
1504 if (dirDisplay.attr("dirid") && rootPath && rootId) {
1505 var lastSlash = rootPath.lastIndexOf(platformSpecificSeparator);
1506 var lastPipe = rootId.lastIndexOf("|");
1507 //If rootPath matches the format of a file path (e.g. C:\), parentPath is everything from the start of the string
1508 //to the last \, beyond which is the id of the current directory.
1509 var parentPath = (rootPath.match(/^[A-Z]:\\$/)) ? ("") : (rootPath.substring(0, lastSlash));
1510 //If parentPath is now only a drive designation (e.g. C:), add the \ back on.
1511 if (parentPath.match(/^[A-Z]:$/)) {
1512 parentPath += "\\";
1513 }
1514 var parentId = rootId.substring(0, lastPipe);
1515 html += '<div class="parentDirRow" onclick="getDirs(\'' + parentPath.replace(/\\/g, "\\\\") + '\', \'' + parentId + '\', \'' + platformSpecificSeparator.replace(/\\/g, "\\\\") + '\')"><span class="parentDirIcon"></span><span>' + getString("parentdir") + '</span></div>';
1516 }
1517 else {
1518 html += '<div class="parentDirRow"></div>';
1519 }
1520 $.each(responsePieces, function(i, value){
1521 if (value.length > 1) {
1522
1523 // directory/file id is 3+ digits long
1524 var ii = 3;
1525 while ((value.charAt(ii) != "D" && value.charAt(ii) != "F") && ii < value.length) {
1526 ii = ii + 1;
1527 }
1528
1529 var dirId = value.substring(0, ii);
1530 var fullId = dirId;
1531 if (rootId) {
1532 fullId = rootId + "|" + dirId;
1533 }
1534
1535 var dirKey = value.charAt(ii);
1536 var dirPath = value.substring(ii + 1);
1537 var fullPath = dirPath;
1538
1539 if (rootPath) {
1540 var separatorChar = (rootPath.lastIndexOf(platformSpecificSeparator) != rootPath.length - 1) ? (platformSpecificSeparator) : ("");
1541 fullPath = rootPath + separatorChar + dirPath;
1542 }
1543 if (dirKey == "D") {
1544 html += '<div class="dirRow" onclick="getDirs(\'' + fullPath.replace(/\\/g, "\\\\") + '\', \'' + fullId + '\', \'' + platformSpecificSeparator.replace(/\\/g, "\\\\") + '\')"><span class="dirIcon"></span><span>' + dirPath + '</span></div>';
1545 }
1546 }
1547 });
1548 $("#dirDisplayContainer").html(html);
1549 //If the container is too tall, add the scroll class to it to prevent it from taking up too much real estate.
1550 var wHeight = getWindowHeight();
1551 var dirDisplayMaxHeight = wHeight - Math.round(wHeight/2) - $("#dirPathDisplay").outerHeight() - $("#dialogButtonContainer").outerHeight();
1552 if (parseInt($("#dirDisplayContainer").css("height")) > dirDisplayMaxHeight) {
1553 ($("#dirDisplayContainer")).addClass("scroll");
1554 if (dirDisplayMaxHeight < 150) dirDisplayMaxHeight = 150;
1555 $(".scroll").css("height", dirDisplayMaxHeight);
1556 }
1557 else {
1558 ($("#dirDisplayContainer")).removeClass("scroll");
1559 }
1560}
1561
1562//Get the directories under a given directory id.
1563//dirPath: The path of the previous directory to be used for breadcrumb navigation.
1564//dirId: The id to use for the new dirs call.
1565function getDirs(dirPath, dirId, platformSpecificSeparator){
1566 var passId = dirId.replace(/\|/g, platformSpecificSeparator)
1567 makeGetRequest("/rpc/dirs", {
1568 "path": passId
1569 }, function(response){
1570 populateDirs(response, dirPath, dirId);
1571 });
1572}
1573
1574//Create a new shared folder row for directory browsing. Do this only if the user hasn't chosen a directory for the last
1575//existing row to avoid duplicate blank rows getting stacked up.
1576function createNewSharedFolderRow(){
1577 if ($(".pathInput:last", "#share_folders_container").val()) {
1578 var sharingRows = $(".sharingRowWrapper", "#share_folders_container");
1579 //Unbind the change listener, since only the last row should be listening to add a fresh row when a value is set.
1580 sharingRows.unbind("change");
1581 var i = sharingRows.length;
1582 var html = getNewSharedFolderRowHtml(i+1);
1583 $("#share_folders_container").append(html);
1584 }
1585}
1586
1587//Get the HTML for a shared folder row.
1588//i: The row number. Use this to uniquely identify the row.
1589function getNewSharedFolderRowHtml(i){
1590 folderBrowseDialogMsg = 1;
1591 return '<div class="sharingRowWrapper">\
1592 <input class="sharedCheckbox floatL" onclick="uncheckAggregationBox($(this))" type="checkbox" checked="true"/><input id="pathInput' + i + '" class="longInput pathInput floatL" type="text" onchange="createNewSharedFolderRow()" value=""/>\
1593 <select class="contentTypeDropdown floatL">\
1594 <option value="A">'+getString("allcontenttypes")+'</option>\
1595 <option value="M">'+getString("music")+'</option>\
1596 <option value="P">'+getString("photos")+'</option>\
1597 <option value="V">'+getString("videos")+'</option>\
1598 <option value="m">'+getString("photos")+' & '+getString("videos")+'</option>\
1599 <option value="p">'+getString("music")+' & '+getString("videos")+'</option>\
1600 <option value="v">'+getString("music")+' & '+getString("photos")+'</option>\
1601 </select>\
1602 <a class="actionbtn floatL" onclick="showFolderBrowse(' +
1603 i +
1604 ')" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
1605 getString("browse") +
1606 '</span><span class="actionbtn_r"></span></a>\
1607 <input id="aggCheckbox' +
1608 i +
1609 '" class="aggregationCheckbox floatL" onclick="checkShareBox($(this))" type="checkbox"/>' +
1610 '<label for="aggCheckbox' +
1611 i +
1612 '">' +
1613 getString("shareforagg") +
1614 '</label>\
1615 <div class="clear">\
1616 </div>\
1617 </div>';
1618}
1619
1620
1621// update the receiver list and the aggregation server list
1622function updateMediaReceivers(element, data){
1623 // if user has made changes, do not update the receiver and aggregation server list
1624 if (sharingReceiverChanged) return;
1625 if (!data) {
1626 element.hide();
1627 elementa.hide();
1628 return;
1629 }
1630 data.pop(); //delete last element
1631 // count the number of receivers and aggregation server
1632 var mrec = 0;
1633 var aggs = 0;
1634 for (var key in data) {
1635 var p = data[key].split("\n");
1636 if (p[3] == 0) mrec ++;
1637 else aggs ++;
1638 }
1639 // media receivers list
1640 var html = "";
1641 // set header if there are receivers
1642 if (mrec > 0) {
1643 html = '<tr>\
1644 <th>\
1645 </th>\
1646 <th>\
1647 <span>' + getString("mac") + '</span>\
1648 </th>\
1649 <th>\
1650 <span>' +
1651 getString("ip") +
1652 '</span>\
1653 </th>\
1654 <th>\
1655 <span>' +
1656 getString("friendlyname") +
1657 '</span>\
1658 </th>\
1659 <th>\
1660 <span>' +
1661 getString("receivertype") +
1662 '</span>\
1663 </th>\
1664 <th>\
1665 <span>' +
1666 getString("navtype") +
1667 '</span>\
1668 </th>\
1669 </tr>';
1670 }
1671 // aggregation server list
1672 var elementa = $("[key=aggservers]");
1673 var htmla = "";
1674 // set header if there are aggregation server
1675 if (aggs > 0) {
1676 htmla = '<tr>\
1677 <th>\
1678 <span>' + getString("mac") + '</span>\
1679 </th>\
1680 <th>\
1681 <span>' +
1682 getString("ip") +
1683 '</span>\
1684 </th>\
1685 <th>\
1686 <span>' +
1687 getString("friendlyname") +
1688 '</span>\
1689 </th>\
1690 </tr>';
1691 }
1692 $.each(data, function(i, value){
1693 var receiverPieces = value.split("\n");
1694 var receiver = {};
1695 receiver["id"] = receiverPieces[0];
1696 receiver["mac"] = receiverPieces[1];
1697 receiver["ip"] = receiverPieces[2];
1698 receiver["isAggregation"] = receiverPieces[3];
1699 receiver["enabled"] = receiverPieces[4];
1700 receiver["clientName"] = receiverPieces[5];
1701 receiver["icon"] = receiverPieces[6];
1702 receiver["iconMimeType"] = receiverPieces[7];
1703 receiver["viewName"] = receiverPieces[8];
1704 receiver["hasDefaultView"] = receiverPieces[9];
1705 receiver["friendlyname"] = receiverPieces[10];
1706 var checked = (receiver.enabled == 1) ? ("checked") : ("");
1707 if (receiver.isAggregation == 0) {
1708 html += '<tr id="receiverRow' + i + '">\
1709 <td>\
1710 <input onchange="receiverChanged(' +
1711 i +
1712 ')" type="checkbox" ' +
1713 checked +
1714 '/>\
1715 </td>\
1716 <td>\
1717 <input class="macInput" type="text" value="' +
1718 receiver.mac +
1719 '" disabled="true" style="cursor:default" />\
1720 </td>\
1721 <td>\
1722 <input class="ipInput" type="text" value="' +
1723 receiver.ip +
1724 '" disabled="true" style="cursor:default" />\
1725 </td>\
1726 <td>\
1727 <input class="fnameInput" type="text" value="' +
1728 receiver.friendlyname +
1729 '" disabled="true" title="' + receiver.friendlyname + '" alt="' + receiver.friendlyname + '" style="cursor:default" />\
1730 </td>\
1731 <td>' +
1732 getReceiverClientDropdown(receiver.clientName, i) +
1733 '</td>\
1734 <td>' +
1735 getReceiverViewDropdown(receiver.viewName, receiver.hasDefaultView == 1, i) +
1736 '</td>\
1737 </tr>';
1738 } else {
1739 htmla += '<tr id="aggserverRow' + i + '">\
1740 <td>\
1741 <input class="macInput" type="text" value="' +
1742 receiver.mac +
1743 '" disabled="true" style="cursor:default" />\
1744 </td>\
1745 <td>\
1746 <input class="ipInput" type="text" value="' +
1747 receiver.ip +
1748 '" disabled="true" style="cursor:default" />\
1749 </td>\
1750 <td>\
1751 <input class="fnameInput" type="text" value="' +
1752 receiver.friendlyname +
1753 '" disabled="true" title="' + receiver.friendlyname + '" alt="' + receiver.friendlyname + '" style="cursor:default" />\
1754 </td>\
1755 </tr>';
1756 }
1757 });
1758 element.html("");
1759 element.show();
1760 element.append(html);
1761 elementa.html("");
1762 elementa.show();
1763 elementa.append(htmla);
1764}
1765function updateSharedFolderList(element, data){
1766 // shared folder list
1767 element.html("");
1768 var html = "";
1769 var dirPairs = splitContentDirs(data);
1770 var html = "";
1771 if ((dirPairs.length == 1) && (dirPairs[0] == "")) {
1772 element.append(getNewSharedFolderRowHtml(dirPairs.length));
1773 return;
1774 }
1775 // list the content dirs
1776 $.each(dirPairs, function(i, value){
1777 var dirPieces = value.split("|");
1778 var dirPath = dirPieces[1];
1779 var dirKeys = dirPieces[0];
1780 var enabledKey = dirKeys.substring(0, 1);
1781 var mediaKey = dirKeys.substring(1, 2);
1782 var shared = (enabledKey == "+" || enabledKey == "*") ? ("checked") : ("");
1783 var enabledForAgg = (enabledKey == "*") ? ("checked") : ("");
1784 html += '<div class="sharingRowWrapper">\
1785 <input class="sharedCheckbox floatL" onclick="uncheckAggregationBox($(this))" type="checkbox" ' + shared + '/><input id="pathInput' + i + '" class="longInput pathInput floatL" type="text" value="' + dirPath + '"/>\
1786 <select class="contentTypeDropdown floatL">\
1787 <option value="A" ' +
1788 checkSelectedMediaOption(mediaKey, "A") +
1789 '>'+getString("allcontenttypes")+'</option>\
1790 <option value="M" ' +
1791 checkSelectedMediaOption(mediaKey, "M") +
1792 '>'+getString("music")+'</option>\
1793 <option value="P" ' +
1794 checkSelectedMediaOption(mediaKey, "P") +
1795 '>'+getString("photos")+'</option>\
1796 <option value="V" ' +
1797 checkSelectedMediaOption(mediaKey, "V") +
1798 '>'+getString("videos")+'</option>\
1799 <option value="m" ' +
1800 checkSelectedMediaOption(mediaKey, "m") +
1801 '>'+getString("photos")+' & '+getString("videos")+'</option>\
1802 <option value="p" ' +
1803 checkSelectedMediaOption(mediaKey, "p") +
1804 '>'+getString("music")+' & '+getString("videos")+'</option>\
1805 <option value="v" ' +
1806 checkSelectedMediaOption(mediaKey, "v") +
1807 '>'+getString("music")+' & '+getString("photos")+'</option>\
1808 </select>\
1809 <a class="actionbtn floatL" onclick="showFolderBrowse(' +
1810 i +
1811 ')" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
1812 getString("browse") +
1813 '</span><span class="actionbtn_r"></span></a>\
1814 <input id="aggCheckbox' +
1815 i +
1816 '" class="aggregationCheckbox floatL" onclick="checkShareBox($(this))" type="checkbox" ' +
1817 enabledForAgg +
1818 '/>' +
1819 '<label for="aggCheckbox' +
1820 i +
1821 '">' +
1822 getString("shareforagg") +
1823 '</label>\
1824 <div class="clear">\
1825 </div>\
1826 </div>';
1827 });
1828 element.html(html);
1829 element.append(getNewSharedFolderRowHtml(dirPairs.length + 1));
1830}
1831
1832function submitSharingData(){
1833 var args = submitSharingData.arguments.length;
1834 returnedCalls = 0;
1835 expectedCalls = 1;
1836 showLoadingGraphic();
1837 hideActionButtons();
1838 resetChanged();
1839 //Only submit client_add requests for receivers that have been changed.
1840 sharingReceiverChanged = false;
1841 $.each(changedReceivers, function(key, receiverRow){
1842 expectedCalls++;
1843 var enabled = ($("input[type=checkbox]", receiverRow).attr("checked")) ? ("1") : ("0");
1844 var clientId = $("select[name=clientType]", receiverRow).val();
1845 var viewName = $("select[name=viewName]", receiverRow).val();
1846 var mac = $(".macInput", receiverRow).val();
1847 makeGetRequest("/rpc/client_add", {
1848 "mac": mac,
1849 "id": clientId,
1850 "enabled": enabled,
1851 "view": viewName
1852 }, function(){
1853 returnedCalls++;
1854 finishSaving();
1855 });
1856 });
1857
1858 var shareFoldersList = $(".sharingRowWrapper", $("#share_folders_container"));
1859 var emptyPath = false;
1860 var contentDirKey = "contentdir=";
1861 var contentDir = "contentdir=";
1862 $.each(shareFoldersList, function(i, value){
1863 var dirInput = $(".pathInput", value);
1864 var dirPath = dirInput.val();
1865 if (dirPath) {
1866 var sharedCheckbox = $(".sharedCheckbox", value);
1867 var aggregationCheckbox = $(".aggregationCheckbox", value);
1868 var enabledKey;
1869 if (sharedCheckbox.is(":checked") && aggregationCheckbox.is(":checked")) {
1870 enabledKey = "*";
1871 }
1872 else
1873 if (sharedCheckbox.is(":checked")) {
1874 enabledKey = "+";
1875 }
1876 else {
1877 enabledKey = "-";
1878 }
1879 var mediaKey = $(".contentTypeDropdown", value).val();
1880 contentDir += enabledKey + mediaKey + "|" + dirPath + ",";
1881 }
1882 else emptyPath = true;
1883 });
1884 if (contentDir.length > contentDirKey.length) contentDir = contentDir.substring(0, contentDir.length - 1);
1885 var data = "";
1886 data += contentDir + "\n";
1887 var autoshareCheckbox = $("#autoshareCheckbox", $(".serverSettingsContentWrapper"));
1888 var autoshareCheckboxValue = ((autoshareCheckbox.is(":checked")) ? (1) : (0));
1889 if (sharingOrig["rmautoshare"] != autoshareCheckboxValue) data += "rmautoshare=" + autoshareCheckboxValue + "\n";
1890 var clientautoenable = (($("input[key=clientautoenable]").attr("checked") == true) ? ("1") : ("0"));
1891 if (sharingOrig["clientautoenable"] != clientautoenable) data += "clientautoenable=" + clientautoenable + "\n";
1892 //var data = contentDir + "\n" + autoshareRmEnabled + "\n" + clientautoenable + "\n";
1893 makePostRequest("/rpc/set_all", {}, data, function(){
1894 returnedCalls++;
1895 finishSaving();
1896 hideLoadingGraphic();
1897 if ((args > 0) && emptyPath) {
1898 updateSharedFolderList($("[key=contentdir]"), contentDir.substring(contentDirKey.length, contentDir.length))
1899 }
1900 });
1901}
1902
1903
1904// ------------------------
1905// aggregation page
1906// ------------------------
1907function loadAggregation(){
1908 returnedCalls = 0;
1909 expectedCalls = 2;
1910 saveHandler = "submitAggregationData();"
1911 inputFieldClicked = false;
1912 changesMade = false;
1913
1914 showLoadingGraphic();
1915
1916 makeGetRequest("/rpc/get_all", {}, function(response){
1917 parseSeparatedData(response, aggregation, aggregationOrig, "=");
1918 returnedCalls++;
1919 if (expectedCalls == returnedCalls) {
1920 loadAggregationHtml();
1921 hideLoadingGraphic();
1922 }
1923 });
1924
1925 //The listaggregatedservers call will fail if aggregation is disabled. If data is returned, handle it as normal.
1926 //Otherwise, make sure the aggregated servers collection is empty and load the HTML.
1927 $.ajax("/rpc/listaggregatedservers", {
1928 success: function(response){
1929 parseData(response, aggregation, aggregationOrig, "aggregatedservers", "--");
1930 returnedCalls++;
1931 if (expectedCalls == returnedCalls) {
1932 loadAggregationHtml();
1933 hideLoadingGraphic();
1934 }
1935 },
1936 error: function(){
1937 aggregation["aggregatedservers"] = null;
1938 returnedCalls++;
1939 if (expectedCalls == returnedCalls) {
1940 loadAggregationHtml();
1941 hideLoadingGraphic();
1942 }
1943 }
1944 });
1945}
1946
1947function loadAggregationHtml(){
1948 makeGetRequest("/webconfig/aggregation.htm", {}, function(response){
1949 var responseHtml = $(response);
1950 replaceStrings(responseHtml);
1951 replaceData(responseHtml, aggregation, handleAggregationData);
1952 showToggleButtons(responseHtml);
1953
1954 $(".serverSettingsContentWrapper").html(responseHtml);
1955 //Show or hide the aggregation server container based on whether or not aggregation is enabled.
1956 toggleAvailableServers(aggregation["aggregation"] == 1);
1957 $("input,select", "#aggregationContainer").live("change", onEventChange);
1958 highlightNav($("#nav_aggregation"));
1959 updateTimer = setInterval(updateAggregation, updateTimerInterval);
1960 });
1961}
1962
1963function updateAggregation(){
1964 //Only call to get an updated list of servers if no changes have been made. This avoids
1965 // updating HTML while a user is making changes.
1966 if(!changesMade) {
1967 $.ajax("/rpc/listaggregatedservers", {
1968 success: function(response){
1969 parseData(response, aggregation, aggregationOrig, "aggregatedservers", "--");
1970 updateAggregatedServers($("[key=aggregatedservers]"), aggregation.aggregatedservers);
1971 },
1972 error: function(){
1973 aggregation["aggregatedservers"] = null;
1974 }
1975 });
1976 }
1977}
1978
1979function handleAggregationData(element, key, data){
1980 switch (key) {
1981 case "aggregation":
1982 if (data == 1) {
1983 element.attr("checked", true);
1984 }
1985 break;
1986 case "aggmode":
1987 var matchingInput = $("input[type=radio][name=aggregationMode][value=" + data + "]", element);
1988 matchingInput.attr("checked", true);
1989 break;
1990 case "aggregatedservers":
1991 updateAggregatedServers(element, data);
1992 break;
1993 }
1994}
1995
1996function updateAggregatedServers(element, data){
1997 if (data) {
1998 var serverHtml = "";
1999 //Pop the last element of the collection off, since it's an empty piece of data.
2000 data.pop();
2001 $.each(data, function(i, value){
2002 var serverDataPieces = value.split("<br>");
2003 var serverData = {};
2004 $.each(serverDataPieces, function(i, value){
2005 var dataKey = value.substring(0, 1);
2006 var dataValue = value.substring(2, value.length).replace(/\n/g, "");
2007 serverData[dataKey] = dataValue;
2008 });
2009 var musicChecked = "";
2010 var photosChecked = "";
2011 var videosChecked = "";
2012 switch (serverData["F"]) {
2013 case "A":
2014 musicChecked = "checked";
2015 photosChecked = "checked";
2016 videosChecked = "checked";
2017 break;
2018 case "M":
2019 musicChecked = "checked";
2020 break;
2021 case "P":
2022 photosChecked = "checked";
2023 break;
2024 case "V":
2025 videosChecked = "checked";
2026 break;
2027 case "m":
2028 photosChecked = "checked";
2029 videosChecked = "checked";
2030 break;
2031 case "p":
2032 musicChecked = "checked";
2033 videosChecked = "checked";
2034 break;
2035 case "v":
2036 musicChecked = "checked";
2037 photosChecked = "checked";
2038 break;
2039 }
2040
2041 serverHtml += '<div uuid="' + serverData["S"] + '" class="availableServerContainer">' +
2042 serverData["N"] +
2043 '<div>\
2044 <span class="serverMediaLabel"><input onclick="onServerChanged(\'' +
2045 serverData["S"] +
2046 '\')" name="songs" type="checkbox"' +
2047 musicChecked +
2048 '/>' +
2049 getString("songs") +
2050 ' ' +
2051 serverData["M"] +
2052 '</span><span class="serverMediaLabel"><input onclick="onServerChanged(\'' +
2053 serverData["S"] +
2054 '\')" name="photos" type="checkbox"' +
2055 photosChecked +
2056 '/>' +
2057 getString("photos") +
2058 ' ' +
2059 serverData["P"] +
2060 '</span><span class="serverMediaLabel"><input onclick="onServerChanged(\'' +
2061 serverData["S"] +
2062 '\')" name="videos" type="checkbox"' +
2063 videosChecked +
2064 '/>' +
2065 getString("videos") +
2066 ' ' +
2067 serverData["V"] +
2068 '</span>\
2069 </div>\
2070 <div class="radioControlWrapper">\
2071 <input onclick="onServerChanged(\'' +
2072 serverData["S"] +
2073 '\')" name="' +
2074 serverData["S"] +
2075 'aggmode" type="radio" ' +
2076 checkAggMode(0, serverData["E"]) +
2077 ' value="0" />\
2078 <div class="radioControlTextWrapper nocaption">\
2079 <div class="radioHeader">' +
2080 getString("ignore") +
2081 '</div>\
2082 </div>\
2083 <div class="clear">\
2084 </div>\
2085 </div>\
2086 <div class="radioControlWrapper">\
2087 <input onclick="onServerChanged(\'' +
2088 serverData["S"] +
2089 '\')" name="' +
2090 serverData["S"] +
2091 'aggmode" type="radio" ' +
2092 checkAggMode(1, serverData["E"]) +
2093 ' value="1" />\
2094 <div class="radioControlTextWrapper nocaption">\
2095 <div class="radioHeader">' +
2096 getString("aggregate") +
2097 '</div>\
2098 </div>\
2099 <div class="clear">\
2100 </div>\
2101 </div>\
2102 <div class="radioControlWrapper">\
2103 <input onclick="onServerChanged(\'' +
2104 serverData["S"] +
2105 '\')" name="' +
2106 serverData["S"] +
2107 'aggmode" type="radio" ' +
2108 checkAggMode(2, serverData["E"]) +
2109 ' value="2" />\
2110 <div class="radioControlTextWrapper nocaption">\
2111 <div class="radioHeader">' +
2112 getString("mirror") +
2113 '</div>\
2114 </div>\
2115 <div class="clear">\
2116 </div>\
2117 </div>\
2118 </div>';
2119 });
2120 element.show();
2121 element.html(serverHtml);
2122 }
2123 else {
2124 element.hide();
2125 }
2126}
2127
2128//A utility function to determine whether a checkbox or radio input should be checked.
2129//aggMode: The value of the input in question.
2130//compareMode: The string to compare to.
2131function checkAggMode(aggMode, compareMode){
2132 return (aggMode == compareMode) ? ("checked") : ("");
2133}
2134
2135//When a server is updated, add it to the changedServers collection.
2136//uuid: The uuid of the changed server.
2137function onServerChanged(uuid){
2138 changedServers[uuid] = uuid;
2139}
2140
2141function toggleAvailableServers(isAggEnabled){
2142 (isAggEnabled) ? ($("#availableServersContainer").show()) : ($("#availableServersContainer").hide());
2143}
2144
2145function submitAggregationData(){
2146 hideActionButtons();
2147 returnedCalls = 0;
2148 expectedCalls = 1;
2149
2150 showLoadingGraphic();
2151 resetChanged();
2152
2153 var aggregationEnabledCheckbox = $("#aggregationEnabledCheckbox");
2154 var enableAggregation = ((aggregationEnabledCheckbox.is(":checked")) ? (1) : (0));
2155 var aggMode = $("input[name=aggregationMode]:checked").val();
2156 //var data = enableAggregation + "\n" + aggMode + "\n";
2157 var data = "";
2158 if (aggregationOrig["aggregation"] != enableAggregation) data += "aggregation=" + enableAggregation + "\n";
2159 if (aggregationOrig["aggmode"] != aggMode) data += "aggmode=" + aggMode + "\n";
2160
2161 var aggregationServers = $(".availableServerContainer");
2162 $.each(aggregationServers, function(i, value){
2163 var element = $(value);
2164 //Only submit aggregatedserverswitch and aggregatedservercontent calls for servers that have been changed
2165 //(are in the changedServers collection).
2166 if (changedServers[element.attr("uuid")]) {
2167 expectedCalls += 2;
2168 var selectedAggregationMode = $("input[type=radio]:checked", element).val();
2169 var selectedContentTypes = $("input[type=checkbox]:checked", element);
2170 var musicChecked = false;
2171 var photosChecked = false;
2172 var videosChecked = false;
2173 $.each(selectedContentTypes, function(i, value){
2174 var checkbox = $(value);
2175 switch (checkbox.attr("name")) {
2176 case "songs":
2177 musicChecked = true;
2178 break;
2179 case "photos":
2180 photosChecked = true;
2181 break;
2182 case "videos":
2183 videosChecked = true;
2184 break;
2185 }
2186 });
2187 var contentType = "";
2188 if (musicChecked && photosChecked && videosChecked) {
2189 contentType = "A";
2190 }
2191 else
2192 if (musicChecked && photosChecked) {
2193 contentType = "v";
2194 }
2195 else
2196 if (musicChecked && videosChecked) {
2197 contentType = "p";
2198 }
2199 else
2200 if (photosChecked && videosChecked) {
2201 contentType = "m";
2202 }
2203 else
2204 if (musicChecked) {
2205 contentType = "M";
2206 }
2207 else
2208 if (photosChecked) {
2209 contentType = "P";
2210 }
2211 else
2212 if (videosChecked) {
2213 contentType = "V";
2214 }
2215 makeGetRequest("/rpc/aggregatedserverswitch", {
2216 "uuid": element.attr("uuid"),
2217 "enabled": selectedAggregationMode
2218 }, function(){
2219 returnedCalls++;
2220 finishSaving();
2221 });
2222 makeGetRequest("/rpc/aggregatedservercontent", {
2223 "uuid": element.attr("uuid"),
2224 "cType": contentType
2225 }, function(){
2226 returnedCalls++;
2227 finishSaving();
2228 });
2229 }
2230 });
2231 makePostRequest("/rpc/set_all", {}, data, function(){
2232 returnedCalls++;
2233 finishSaving();
2234 hideLoadingGraphic();
2235 });
2236}
2237
2238function finishSaving(){
2239 if (returnedCalls == expectedCalls) {
2240 showActionButtons();
2241 makeGetRequest("/rpc/info_status", {}, function(data){
2242 var dataPieces = data.split("\n");
2243 $.each(dataPieces, function(i, value){
2244 var pieces = value.split("|");
2245 if (pieces[0] == "restartpending") {
2246 var restartPending = (pieces[1] == 1);
2247 if (restartPending) {
2248 showDialogOverlay(function(){
2249 return getString("restartprompt")
2250 }, {}, [{
2251 text: getString("ok"),
2252 onclick: "restartServer();"
2253 }, {
2254 text: getString("cancel"),
2255 onclick: "hideDialogOverlay();"
2256 }]);
2257 }
2258 return false;
2259 }
2260 });
2261 });
2262 }
2263}
2264
2265
2266// ------------------------
2267// advanced page
2268// ------------------------
2269function loadAdvanced(){
2270 returnedCalls = 0;
2271 expectedCalls = 6;
2272 saveHandler = "submitAdvancedData();"
2273 inputFieldClicked = false;
2274 changesMade = false;
2275
2276 showLoadingGraphic();
2277
2278 // function get_server_type restored - should be retired again.
2279 makeGetRequest("/rpc/get_server_type", {}, function(response){
2280 advanced["servertype"] = response;
2281 returnedCalls++;
2282 if (expectedCalls == returnedCalls) {
2283 loadAdvancedHtml();
2284 hideLoadingGraphic();
2285 }
2286 });
2287
2288 makeGetRequest("/rpc/get_all", {}, function(response){
2289 parseSeparatedData(response, advanced, advancedOrig, "=");
2290 returnedCalls++;
2291 if (expectedCalls == returnedCalls) {
2292 loadAdvancedHtml();
2293 hideLoadingGraphic();
2294 }
2295 });
2296
2297 makeGetRequest("/rpc/info_status", {}, function(response){
2298 parseSeparatedData(response, advanced, advancedOrig, "|");
2299 // advanced["servertype"] = getServerType(advanced["licensestatus"]);
2300 returnedCalls++;
2301 if (expectedCalls == returnedCalls) {
2302 loadAdvancedHtml();
2303 hideLoadingGraphic();
2304 }
2305 });
2306
2307 makeGetRequest("/rpc/get_portal_info", {}, function(response){
2308 advanced["portalinfo"] = response;
2309 returnedCalls++;
2310 if (expectedCalls == returnedCalls) {
2311 loadAdvancedHtml();
2312 hideLoadingGraphic();
2313 }
2314 });
2315
2316 makeGetRequest("/rpc/get_option?portalusername", {}, function(response){
2317 advanced["portalusername"] = response;
2318 returnedCalls++;
2319 if (expectedCalls == returnedCalls) {
2320 loadAdvancedHtml();
2321 hideLoadingGraphic();
2322 }
2323 });
2324
2325 makeGetRequest("/rpc/get_webdav_link", {}, function(response){
2326 parseData(response, advanced, advancedOrig, "webdavLink", "\n");
2327 returnedCalls++;
2328 if (expectedCalls == returnedCalls) {
2329 loadAdvancedHtml();
2330 hideLoadingGraphic();
2331 }
2332 });
2333}
2334
2335function loadAdvancedHtml(){
2336 makeGetRequest("/webconfig/advanced.htm", {}, function(response){
2337 var responseHtml = $(response);
2338 replaceStrings(responseHtml);
2339 replaceData(responseHtml, advanced, handleAdvancedData);
2340 showToggleButtons(responseHtml);
2341
2342 $(".serverSettingsContentWrapper").html(responseHtml);
2343 hideWebdavContainer(advanced["webdavLink"]);
2344 $("input", "#advancedContainer").live("click", onEventClick);
2345 $("input,select", "#advancedContainer").live("change", onEventChange);
2346 highlightNav($("#nav_advanced"));
2347 });
2348}
2349
2350function hideWebdavContainer(data){
2351 if (data[0].toLowerCase() == "nowebdav") {
2352 ($("#availableWebdavContainer").hide());
2353 } else {
2354 ($("#availableWebdavContainer").show());
2355 }
2356}
2357
2358function handleAdvancedData(element, key, data){
2359 var serverType = advanced["servertype"];
2360 switch (key) {
2361 case "accessuser":
2362 element.val(data);
2363 break;
2364 case "accesspwd":
2365 if (data.length > 0) element.val("_twonkypassword_");
2366 break;
2367 case "myexperience":
2368 // Do not show the header "Improve My Experience" if it is the free server
2369 if (serverType.toLowerCase().lastIndexOf("free") > -1) break;
2370 // Do not show the header "Improve My Experience" if it is a server with license key
2371 if ((advanced["licensestatus"] >= 2) && (advanced["cdkey"].length > 1)) break;
2372 // show this header if it is an OEM server
2373 if (advanced["reportdevice"])
2374 element.show();
2375 break;
2376 case "reportdevice":
2377 element.attr("checked", data > 0)
2378 break;
2379 case "compilationsdir":
2380 case "scantime":
2381 element.val(data);
2382 break;
2383 case "nicrestart":
2384 element.attr("checked", data > 0)
2385 break;
2386 case "v":
2387 element.attr("checked", data > 0)
2388 break;
2389 case "webdavLink":
2390 element.attr("href", data);
2391 break;
2392 case "portaldisassociation":
2393 if (noMediaFusionServer || myTwonkyDisabled) break;
2394 if ((advanced["portalinfo"] != "useronline") && (advanced["portalinfo"] != "notloggedin")) break;
2395 element.show();
2396 break;
2397 case "portalinfo":
2398 if (noMediaFusionServer || myTwonkyDisabled) break;
2399 if ((data != "useronline") && (data != "notloggedin")) break;
2400 var renewStr = "";
2401 var serverTypeShort = "premium";
2402 if (serverType.toLowerCase().lastIndexOf("free") > -1) {
2403 serverTypeShort = "free";
2404 }
2405 var disLink = '<div><a class="actionbtnmd bold" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)"\
2406 onclick="disassociatePortalLink(\'' + serverTypeShort + '\')"><span class="actionbtn_l"></span><span class="actionbtn_c">' +
2407 getString("portaldisassociate") +
2408 '</span><span class="actionbtn_r"></span></a></div>';
2409 switch (data) {
2410 case "useronline":
2411 returnValue = getString("loggedinas") + " " + advanced["portalusername"] + '<br /><br />' + disLink + '<div class="serverContentSpacer"></div>';
2412 element.html(returnValue);
2413 break;
2414 case "notloggedin":
2415 returnValue = getString("loginneededcaption") + '<br /><br />' + disLink + '<div class="serverContentSpacer"></div>';
2416 element.html(returnValue);
2417 break;
2418 }
2419 }
2420}
2421
2422
2423
2424
2425//Get HTML for the receiver navigation view dropdown.
2426//selectedView: The currently selected navigation view for the current receiver.
2427//isDefault: A boolean that indicates whether or not the user is able to update the dropdown. If true, a disabled
2428//dropdown is returned.
2429//i: The row number used to uniquely identify the receiver.
2430function getReceiverViewDropdown(selectedView, isDefault, i){
2431 var html = "";
2432 if (!isDefault) {
2433 html = '<select name="viewName" onchange="receiverChanged(' + i + ')">\
2434 <option value="mobile" ' +
2435 checkSelectedMediaOption("mobile", selectedView) +
2436 '>' +
2437 getString("mobile") +
2438 '</option>\
2439 <option value="simpledefault" ' +
2440 checkSelectedMediaOption("simpledefault", selectedView) +
2441 '>' +
2442 getString("simpledefault") +
2443 '</option>\
2444 <option value="ipodlike" ' +
2445 checkSelectedMediaOption("ipodlike", selectedView) +
2446 '>' +
2447 getString("ipodlike") +
2448 '</option>\
2449 <option value="byfolder" ' +
2450 checkSelectedMediaOption("byfolder", selectedView) +
2451 '>' +
2452 getString("byfolder") +
2453 '</option>\
2454 <option value="advanceddefault" ' +
2455 checkSelectedMediaOption("advanceddefault", selectedView) +
2456 '>' +
2457 getString("advanceddefault") +
2458 '</option>\
2459 </select>';
2460 }
2461 else {
2462 html = '';
2463 }
2464 return html;
2465}
2466
2467//Get HTML for the receiver client type dropdown.
2468//selectedView: The currently selected navigation view for the current receiver.
2469//i: The row number used to uniquely identify the receiver.
2470function getReceiverClientDropdown(selectedClient, i){
2471 var html = '<select class="clientType" name="clientType" onchange="receiverChanged(' + i + ')">';
2472 var clientPieces = sharing.clients[0].split(",");
2473 $.each(clientPieces, function(i, value){
2474 if (i % 2 == 0) {
2475 html += '<option value="' + value + '" ';
2476 }
2477 else {
2478 html += checkSelectedMediaOption(value, selectedClient) + '>' + value + '</option>';
2479 }
2480 });
2481 html += "</select>";
2482 return html;
2483}
2484
2485//Add a changed receiver to the changedReceviers collection.
2486//i: The row number of the changed receiver.
2487function receiverChanged(i){
2488 sharingReceiverChanged = true;
2489 changedReceivers[i] = $("#receiverRow" + i, ".mediaReceiversTable");
2490}
2491
2492function clearPassword() {
2493 $("#password").val("");
2494}
2495
2496function submitAdvancedData(){
2497 var accessCredentialChanged = false;
2498 hideActionButtons();
2499 returnedCalls = 0;
2500 expectedCalls = 1;
2501
2502 showLoadingGraphic();
2503 resetChanged();
2504
2505 var compilations = $("#compilations", ".accountSettingsContainer").val();
2506 var rescan = $("#rescan", ".accountSettingsContainer").val();
2507 var nicrestart = (($("#nicRestartEnabledCheckbox").attr("checked") == true) ? ("1") : ("0"));
2508 var myexperience = (($("#myExperienceEnabledCheckbox").attr("checked") == true) ? ("1") : ("0"));
2509 var loggingEnabled = (($("#loggingEnabledCheckbox").attr("checked") == true) ? ("4095") : ("0"));
2510 var data = "";
2511 if (advancedOrig["compilationsdir"] != compilations) data += "compilationsdir=" + compilations + "\n";
2512 if (advancedOrig["scantime"] != rescan) data += "scantime=" + rescan + "\n";
2513 if (advancedOrig["nicrestart"] != nicrestart) data += "nicrestart=" + nicrestart + "\n";
2514 if (advancedOrig["myexperience"] != myexperience) data += "reportdevice=" + myexperience + "\n";
2515 if (advancedOrig["v"] != loggingEnabled) data += "v=" + loggingEnabled + "\n";
2516
2517 if (!($("#password", ".accountSettingsContainer").val() == "_twonkypassword_")) {
2518 var username = "accessuser=" + $("#username", ".accountSettingsContainer").val();
2519 var password = "accesspwd=" + $("#password", ".accountSettingsContainer").val();
2520 if (((username.length > 11) && (password.length > 10)) || ((username.length == 11) && (password.length == 10))) {
2521 // set new username password
2522 data += username + "\n" + password + "\n";
2523 accessCredentialChanged = true;
2524 } else {
2525 // reset username and password (only username or password was entered)
2526 $("#username").val(advancedOrig["accessuser"]);
2527 if (advancedOrig["accesspwd"].length > 0) $("#password").val("_twonkypassword_");
2528 else $("#password").val("");
2529 showDialogOverlay(function(){
2530 return getString("notchanged")
2531 }, {}, [{
2532 text: getString("ok"),
2533 onclick: "hideDialogOverlay();"
2534 }]);
2535 }
2536 }
2537 makePostRequest("/rpc/set_all", {}, data, function(){
2538 returnedCalls++;
2539 finishSaving();
2540 hideLoadingGraphic();
2541 //Refresh the page if the user has changed the username or password to make sure the user is prompted correctly.
2542 //if ($("#username", ".accountSettingsContainer").val() != advanced["accessuser"] || $("#password", ".accountSettingsContainer").val() != advanced["accesspwd"]) {
2543 if (accessCredentialChanged) {
2544 updateWebDavAdmin($("#username", ".accountSettingsContainer").val(), $("#password", ".accountSettingsContainer").val()); // change also the WebDav server admin credentials
2545 location.reload();
2546 }
2547 });
2548}
2549
2550function updateWebDavAdmin(user, pass) {
2551 var request = loadXMLDoc("/update_admin_account?user="+user+"&pass="+pass,"");
2552 if (request.status < 200 && request.status > 300)
2553 showDialog(getString("dialog_admin_acc_could_not_update"));
2554 else {
2555 admin_user_name = user;
2556 admin_password = pass;
2557 }
2558}
2559
2560var restartTest;
2561function restartServer(){
2562 makeGetRequest("/rpc/restart", {}, function(){
2563 showDialogOverlay(function(){
2564 return "<div class='spinner floatL'></div><div style='padding: 5px 0px 0px 10px;' class='floatL'>" + getString("serverrestarting") + "</div>"
2565 }, {}, {});
2566 restartTest = setInterval(function(){
2567 makeGetRequest("/rpc/get_all", {}, function(){
2568 clearInterval(restartTest);
2569 hideDialogOverlay();
2570 // Update aggregation info after the restart is complete on the Aggregation page to display the list of servers more quickly.
2571 if(window.location.hash == '#aggregation') {
2572 // Callback must be on a short timeout in order to work since rpc call needs a short amount of time to popuplate list of
2573 // aggregation servers after restarting, if call is done without timeout, response is still empty.
2574 setTimeout("updateAggregation()", 1500);
2575 }
2576 })
2577 }, 1000);
2578 });
2579}
2580
2581function rescanFolders(){
2582 makeGetRequest("/rpc/rescan", {}, null);
2583}
2584
2585//Display a dialog that prompts the user before completing a server reset.
2586function promptReset(){
2587 showDialogOverlay(function(){
2588 return getString("resetprompt");
2589 }, null, {
2590 1: {
2591 text: getString("ok"),
2592 onclick: "hideDialogOverlay(); resetServer()"
2593 },
2594 2: {
2595 text: getString("cancel"),
2596 onclick: "hideDialogOverlay()"
2597 }
2598 });
2599}
2600
2601var resetTest;
2602function resetServer(){
2603 makeGetRequest("/rpc/reset", {}, function(){
2604 showDialogOverlay(function(){
2605 return "<div class='spinner floatL'></div><div style='padding: 5px 0px 0px 10px;' class='floatL'>" + getString("serverrestarting") + "</div>"
2606 }, {}, {});
2607 var t = setTimeout("waitingForServer()", 3000);
2608 });
2609}
2610function waitingForServer() {
2611 resetTest = setInterval(function(){
2612 makeGetRequest("/rpc/get_all", {}, function(response){
2613 if (response != "") {
2614 clearInterval(resetTest);
2615 setReloadSide = true;
2616 hideDialogOverlay();
2617 }
2618 })
2619 }, 2000);
2620}
2621
2622function clearCache(){
2623 makeGetRequest("/rpc/clear_cache", {}, null);
2624}
2625
2626
2627var resetTest;
2628function resetClients(){
2629 makeGetRequest("/rpc/resetclients", {}, function(){
2630 showDialogOverlay(function(){
2631 return "<div class='spinner floatL'></div><div style='padding: 5px 0px 0px 10px;' class='floatL'>" + getString("clientreset") + "</div>"
2632 }, {}, {});
2633 resetTest = setInterval(function(){
2634 makeGetRequest("/rpc/get_all", {}, function(){
2635 clearInterval(resetTest);
2636 hideDialogOverlay();
2637 loadSharing();
2638 })
2639 }, 1000);
2640 });
2641}
2642
2643function viewLog(){
2644 window.open("/rpc/log_getfile", "_blank");
2645}
2646
2647function clearLog(){
2648 makeGetRequest("/rpc/log_clear", {}, null);
2649}
2650
2651//Add the active class to a button when the mouse is pressed.
2652function onButtonMouseDown(button){
2653 var button = $(button);
2654 button.addClass("active");
2655}
2656
2657//Remove the active class from a button when the mouse is released.
2658function onButtonMouseUp(button){
2659 var button = $(button);
2660 button.removeClass("active");
2661}
2662
2663function cancelSettings(){
2664 resetChanged();
2665 $(window).trigger("hashchange");
2666}
2667
2668function showActionButtons(){
2669 $("#actionButtonContainer").show();
2670 $("#spinnerContainer").hide();
2671}
2672
2673function hideActionButtons(){
2674 $("#actionButtonContainer").hide();
2675 $("#spinnerContainer").show();
2676}
2677
2678//Display a dialog overlay and opaque the background to prevent the user from interacting with the page until
2679//the dialog is closed.
2680//contentConstructor: The function to be called in order to populate the contents of the dialog.
2681//contentArgs: Arguments to be passed to the content constructor.
2682//buttons: A collection of buttons to include in the dialog. Buttons should be in the format {"text": text, "onclick":
2683//"onClickFunction()"}, where text is the text to be shown on the button and onclick is the function to be called
2684//when the button is clicked, expressed as a string ("onClickFunction()" rather than onClickFunction).
2685function showDialogOverlay(contentConstructor, contentArgs, buttons, widthClass){
2686 var dialog = $("#dialogOverlay");
2687 if (dialog) {
2688 dialog.remove();
2689 }
2690 var body = $(document.body);
2691 var contentHtml = contentConstructor(contentArgs);
2692 var buttonHtml = makeButtons(buttons);
2693 var overlay = $("#overlay");
2694 if (overlay.length < 1) {
2695 body.append("<div id='overlay' class='overlay'></div>");
2696 }
2697 body.append('<div id="dialogOverlay" class="dialogWrapper ' + widthClass + '"> \
2698 <b class="dialogTop"><b class="d1"></b><b class="d2"></b><b class="d3"></b><b class="d4"></b></b> \
2699 <div class="dialogContentWrapper">\
2700 <div class="dialogContent">\
2701 ' +
2702 contentHtml +
2703 '\
2704 <div class="dialogButtonContainer" id ="dialogButtonContainer">\
2705 ' +
2706 buttonHtml +
2707 '\
2708 </div>\
2709 <div class="clear"></div>\
2710 </div>\
2711 </div>\
2712 <b class="dialogBottom"><b class="d4"></b><b class="d3"></b><b class="d2"></b><b class="d1"></b></b></div>');
2713 var dialog = $("#dialogOverlay");
2714 var dialogWidth = dialog.outerWidth();
2715 var left = (body.width() / 2) - (dialogWidth / 2);
2716 dialog.css("left", left);
2717 if (contentArgs && contentArgs.onstart) {
2718 contentArgs.onstart();
2719 }
2720}
2721
2722//Iterate through the collection of buttons to produce the HTML for them.
2723//buttons: The collection of button objects.
2724function makeButtons(buttons){
2725 var buttonHtml = "";
2726 $.each(buttons, function(key, button){
2727 buttonHtml += '\
2728 <a class="actionbtnmd floatL" onclick="' + button.onclick + '" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)">\
2729 <span class="actionbtn_l"></span>\
2730 <span class="actionbtn_c">' +
2731 button.text +
2732 '</span>\
2733 <span class="actionbtn_r"></span>\
2734 </a>';
2735 });
2736 return buttonHtml;
2737}
2738
2739//Remove the dialog and opaque overlay.
2740function hideDialogOverlay(){
2741 var overlay = $("#overlay");
2742 overlay.remove();
2743 var dialog = $("#dialogOverlay");
2744 dialog.remove();
2745 if (setReloadSide) reloadSide();
2746 setReloadSide = false;
2747}
2748
2749//A function to wrap retrieved JSON data in parentheses for eval-ing to prevent errors.
2750function parseJson(jsonData){
2751 return eval("(" + jsonData + ")");
2752}
2753
2754var mediaBrowsePageCount;
2755//The number of items to display on a page of content.
2756var smallMediaBrowsePageCount = 25;
2757var largeMediaBrowsePageCount = 35;
2758
2759//Clear the center display area and update the left navigation for media browsing.
2760//id: The id of the media category (music, video, photo) to display.
2761//callback: An optional callback to be called after the media browse view has been initialized.
2762function loadMediaBrowse(id, callback){
2763 //If the left navigation hasn't been populated, fetch the left nav HTML.
2764 if ($("#leftColumn").length == 0) {
2765 makeGetRequest("/webconfig/browse-nav.htm", {}, function(response){
2766 var responseHtml = $(response);
2767 replaceStrings(responseHtml);
2768 $("#leftNavContainer").html(responseHtml);
2769 $(".serverSettingsContentWrapper").html('<div class="breadcrumb"></div><div id="browseContents"></div></div><div class="clear"></div><div id="browsePagination"><div class="browsePages largeFont"></div></div>');
2770 $(".serverSettingsContentWrapper").addClass("contentDisplay");
2771 //Call this function again after the essential HTML elements have been created for UI pouplation.
2772 loadMediaBrowse(id, callback);
2773 });
2774 }
2775 //Otherwise, populate the left nav with the options returned from the server.
2776 else {
2777 showLoadingGraphic();
2778 makeGetRequest("/json/feed/" + id, {}, function(response){
2779 var html = "";
2780 //Use the larger page count only for the id 0$2, which corresponds to Photos.
2781 mediaBrowsePageCount = (id == "0$2") ? (largeMediaBrowsePageCount) : (smallMediaBrowsePageCount);
2782 var json = parseJson(response);
2783 $.each(json.containerContents, function(i, data){
2784 if (data.objId == 0) {
2785 var title = data.title;
2786 // replace the myTwonky text with a link to the my.twonky.com web page
2787 if (title.indexOf("myTwonky") >= 0) {
2788 if (browser == "other") {
2789 title = title.replace("myTwonky", "<a class=\'inlineLink\' href=\"javascript:openPortalLink(\'login24\')\">myTwonky</a>");
2790 } else {
2791 // Safari
2792 title = title.replace("myTwonky", "<a class=\'inlineLink\' href=\" " + statusData["portallogin24"] + "\" target=\"_self\" onClick=\"javascript:startPortalCheckTimerOnClick(\'login24\')\">myTwonky</a>");
2793 }
2794 }
2795 html += '<li id="' + data.objId + '" >' + title + '</li>';
2796 } else
2797 if (data.title == "myTwonky") {
2798 // do not show myTwonky in the left navigation tree if disablemytwonky is set
2799 if (!myTwonkyDisabled)
2800 html += '<li id="' + data.objId + '" onclick="navigateTo(\'id=' + data.objId + '&startPage=0&count=' + data.childCount + '\')"><a>' + data.title + '</a></li>';
2801 } else {
2802 html += '<li id="' + data.objId + '" onclick="navigateTo(\'id=' + data.objId + '&startPage=0&count=' + data.childCount + '\')"><a>' + data.title + '</a></li>';
2803 }
2804 });
2805 $("#browseNav").html(html);
2806 if (callback) {
2807 callback();
2808 }
2809 else {
2810 $("#browseContents").html("");
2811 $(".browsePages").html("");
2812 $(".breadcrumb").html("");
2813 }
2814 //If the fragment has an id property in it, extract the id including and immediately after the slash.
2815 //Use this to make sure the correct left nav option is highlighted.
2816 var fragmentPieces = $.param.fragment().split("&");
2817 if (fragmentPieces.length > 1) {
2818 var fragmentId = fragmentPieces[0].split("=")[1];
2819 var activeId = fragmentId.split("$").slice(0, 4);
2820 var activeIdStr = activeId.join("$");
2821 $("#" + activeIdStr.replace(/\$/g, "\\$").replace(/\//g, "\\/")).addClass("current");
2822 }
2823 });
2824 //Apply the active class to the relevant top nav item.
2825 $("a", "#nav").removeClass("active");
2826 var clickedElement = $("#" + id.replace(/\$/g, "\\$"));
2827 clickedElement.addClass("active");
2828 hideLoadingGraphic();
2829 }
2830}
2831
2832//Update the center display area with the selected folder or list of media, along with breadcrumbs and pagination.
2833//id: The id to have its contents displayed.
2834//startItem: The index of the first item to display. Used for pagination.
2835//numItems: The number of child items. Used for building breadcrumb navigation.
2836function loadMediaContents(id, startItem, numItems, reloadContent){
2837 showLoadingGraphic();
2838 var cleanedId = id.replace(/\$/g, "\\$").replace(/\//g, "\\/");
2839 var nullNodes = 0;
2840 var nodes = 0;
2841 //Left nav elements have the same id as their corresponding nodes in the media browse API, so if the id can be
2842 //matched, highlight that element to indicate that it's the currently selected navigation item.
2843 var clickedElement = $("#" + cleanedId);
2844 var clickedElementText = $("a", clickedElement).text();
2845 if ($("#" + cleanedId, "#browseNav").length > 0) {
2846 $("li", "#browseNav").removeClass("current");
2847 $("#" + cleanedId, "#browseNav").addClass("current");
2848 }
2849 var html = "";
2850 var branchHtml = "";
2851 var leafHtml = "";
2852 var pageCount = (mediaBrowsePageCount > numItems) ? (numItems) : (mediaBrowsePageCount);
2853 makeGetRequest("/json/feed/" + id, {
2854 "start": startItem,
2855 "count": pageCount
2856 }, function(response){
2857 var json = parseJson(response);
2858 html += '<div class="subHeader"><span class="subheaderTitle">' + json.containerTitle + '</span></div>';
2859 var photosContainer;
2860 $.each(json.containerContents, function(i, data){
2861 if (data.nodeType == "branch") {
2862 // nodeType is branch - show thumbnail for container if possible
2863 var thumbnail = "";
2864 var objType = "";
2865 if (id.substring(0,3) == "0$1") objType = "M";
2866 if (id.substring(0,3) == "0$2") objType = "P";
2867 if (id.substring(0,3) == "0$3") objType = "V";
2868 // online folders and subfolders have no item count!
2869 // if the event was triggered by the navigation menue (left side) -> was "myTwonky" selected by the user?
2870 // otherwise check the breadcrumb (linked header) -> starts with "myTwonky" (e.g. myTwonky / My Channels)?
2871 var onlineFolder = false;
2872 var clickedBrowseNavElement = false;
2873 if (clickedElementText == "myTwonky") {
2874 onlineFolder = true;
2875 clickedBrowseNavElement = true;
2876 } else {
2877 $("#browseNav li").each(function() {
2878 if ($(this).text() == clickedElementText) clickedBrowseNavElement = true;
2879 });
2880 }
2881 if (!clickedBrowseNavElement) {
2882 if ($(".breadcrumb").length > 0) {
2883 if ($(".breadcrumbItem:first")[0].innerHTML.substring(0, 8) == "myTwonky") onlineFolder = true;
2884 }
2885 }
2886 if (data.thumbnail && objType != "P") {
2887 // container with thumbnail
2888 var thumbnailData = getThumbnailLink(data.thumbnail, objType, true);
2889 thumbnail = '<img class="folderThumbnail" src="' + thumbnailData.link + thumbnailData.scale + '" onerror="loadDefaultThumbnail($(this), \'' + objType + '\')" />';
2890 // show no item count for myTwonky folders
2891 if (onlineFolder)
2892 branchHtml += '<div class="byFolderContainer" onclick="onMediaNodeClicked(this, ' + data.childCount + ')" id="' + data.objId + '"><div id="title"><a class="truncate">' + data.title + '</a><div class="smallFont"> </div></div><div class="folderImageWrapper">'+thumbnail+'</div></div>';
2893 else
2894 branchHtml += '<div class="byFolderContainer" onclick="onMediaNodeClicked(this, ' + data.childCount + ')" id="' + data.objId + '"><div id="title"><a class="truncate">' + data.title + '</a><div class="smallFont">' + data.childCount + ' items</div></div><div class="folderImageWrapper">'+thumbnail+'</div></div>';
2895 } else {
2896 // container with no thumbnail
2897 // show no item count for myTwonky folders
2898 if (onlineFolder)
2899 branchHtml += '<div class="byFolderContainer" onclick="onMediaNodeClicked(this, ' + data.childCount + ')" id="' + data.objId + '"><a class="truncate">' + data.title + '</a><div class="smallFont"> </div><div class="folderImageWrapper"><img class="folderThumbnail" src="/webconfig/spacer.gif" onload="getFolderThumbnail($(this), \'' + data.objId + '\')" /></div></div>';
2900 else
2901 branchHtml += '<div class="byFolderContainer" onclick="onMediaNodeClicked(this, ' + data.childCount + ')" id="' + data.objId + '"><a class="truncate">' + data.title + '</a><div class="smallFont">' + data.childCount + ' items</div><div class="folderImageWrapper"><img class="folderThumbnail" src="/webconfig/spacer.gif" onload="getFolderThumbnail($(this), \'' + data.objId + '\')" /></div></div>';
2902 }
2903 nodes++;
2904 if (data.childCount == 0) nullNodes++;
2905 }
2906 else {
2907 // nodeType is leaf
2908 var thumbnail = "";
2909 if (data.thumbnail && data.objType != "P") {
2910 var thumbnailData = getThumbnailLink(data.thumbnail, data.objType);
2911 thumbnail = '<img src="' + thumbnailData.link + thumbnailData.scale + '" onerror="loadDefaultThumbnail($(this), \'' + data.objType + '\')" />';
2912 }
2913 switch (data.objType) {
2914 case "V":
2915 var duration = data.duration.split(".")[0];
2916 if (duration.length == 0) duration = data.duration;
2917 if (!(duration.indexOf(":") > 0)) {
2918 if (duration.length == 0) duration = "00:00";
2919 if (duration.length == 1) duration = "00:0"+duration;
2920 if (duration.length == 2) duration = "00:"+duration;
2921 }
2922 if (duration.length > 5) {
2923 if (duration.split(":")[0] == "00") {
2924 duration = duration.substring(3, duration.length);
2925 } else {
2926 if (duration.split(":")[0] == "0") {
2927 duration = duration.substring(2, duration.length);
2928 }
2929 }
2930 }
2931 var timeDisplay = (duration) ? ('<div class="timeDisplay">' + duration + '</div>') : ("")
2932 var datamime = data.mime;
2933 //datamime = datamime.replace(/&/g, "&");
2934 //datamime = datamime.replace(/"/g, "\"");
2935 leafHtml += '<div class="myLibraryRow">\
2936 <div class="myLibraryMediaIcon floatL">' + thumbnail + timeDisplay + '</div>\
2937 <div>\
2938 <div class="mediaData"><a class="largeFont" href="' +
2939 data.link +
2940 '" target="_blank">' +
2941 data.title +
2942 '</a></div>\
2943 <div class="mediaData">' +
2944 getString("filesize") +
2945 ' ' +
2946 Math.round((parseInt(data.contentsize) / 1048576) * 100) / 100 +
2947 ' MB</div>\
2948 <div class="mediaData">' +
2949 getString("format") +
2950 ' ' +
2951 datamime +
2952 '</div>\
2953 <div class="mediaData">' +
2954 getString("year") +
2955 ' ' +
2956 data.year +
2957 '</div>\
2958 </div>\
2959 <div class="clear"></div>\
2960 </div>';
2961 break;
2962 case "P":
2963 if (!photosContainer) {
2964 photosContainer = $('<div><div class="allPhotosContainer"></div></div>');
2965 }
2966 var resolutionPieces = data.resolution.split("x");
2967 var width = parseInt(resolutionPieces[0]);
2968 var height = parseInt(resolutionPieces[1]);
2969 var thumbnailData = getThumbnailLink(data.thumbnail, 'P', false, width, height);
2970 // Thumbnails for media fusion feeds are not working because scaling of non local images is not possible
2971 if ((thumbnailData.link.indexOf("httpproxy/embedded") > 0) || (thumbnailData.link.indexOf("httpproxy/direct") > 0))
2972 $(".allPhotosContainer", photosContainer).append('<a href="' + data.link + '" target="_blank"><div class="allPhotosItem"><img src="' + thumbnailData.link + '" onerror="loadDefaultThumbnail($(this), \'P\')" style="' + thumbnailData.clip + '"/></div></a>');
2973 else $(".allPhotosContainer", photosContainer).append('<a href="' + data.link + '" target="_blank"><div class="allPhotosItem"><img src="' + thumbnailData.link + thumbnailData.scale + '" onerror="loadDefaultThumbnail($(this), \'P\')" style="' + thumbnailData.clip + '"/></div></a>');
2974 break;
2975 case "M":
2976 var duration = data.duration.split(".")[0];
2977 if (duration.length == 0) duration = data.duration;
2978 if (!(duration.indexOf(":") > 0)) {
2979 if (duration.length == 0) duration = "00:00";
2980 if (duration.length == 1) duration = "00:0"+duration;
2981 if (duration.length == 2) duration = "00:"+duration;
2982 }
2983 if (duration.length > 5) {
2984 if (duration.split(":")[0] == "00") {
2985 duration = duration.substring(3, duration.length);
2986 } else {
2987 if (duration.split(":")[0] == "0") {
2988 duration = duration.substring(2, duration.length);
2989 }
2990 }
2991 }
2992 if (duration) {
2993 duration = "(" + duration + ")";
2994 }
2995 leafHtml += '<div class="myLibraryListRow">\
2996 <div class="myLibraryListIcon floatL">' + thumbnail + '</div>\
2997 <div>\
2998 <div class="mediaData"><a class="largeFont" href="' +
2999 data.link +
3000 '" target="_blank">' +
3001 data.title +
3002 '</a> <span class="largeFont">' +
3003 duration +
3004 '</span></div>\
3005 <div class="mediaData">' +
3006 getString("artist") +
3007 ' ' +
3008 data.artist +
3009 '</div>\
3010 <div class="mediaData">' +
3011 getString("album") +
3012 ' ' +
3013 data.album +
3014 '</div>\
3015 <div class="mediaData">' +
3016 getString("genre") +
3017 ' ' +
3018 data.genre +
3019 '</div>\
3020 </div>\
3021 <div class="clear"></div>\
3022 </div>';
3023 break;
3024 }
3025 }
3026 });
3027 //Photos require an additional container for layout purposes, so if it exists, add it to the HTML.
3028 if (photosContainer) {
3029 $(".allPhotosContainer", photosContainer).append('<div class="clear"></div>')
3030 leafHtml += photosContainer.html();
3031 }
3032 if (branchHtml) {
3033 branchHtml = '<div>' + branchHtml + '</div><div class="clear"></div>';
3034 }
3035 if (leafHtml) {
3036 leafHtml = '<div>' + leafHtml + '</div>';
3037 //If the media type isn't photos, add a div to provide separation between folder and media display.
3038 //Photos already have a degree of separation because of the extra photos wrapper.
3039 if (branchHtml && !photosContainer) {
3040 leafHtml = '<div class="mixedContentSeparator"></div>' + leafHtml;
3041 }
3042 }
3043 if (branchHtml || leafHtml) {
3044 html += branchHtml + leafHtml;
3045 if ($(".active", "#nav").attr("id") == "0$1") {
3046 makeGetRequest("/json/feed/" + json.parentId, null, function(data){
3047 data = parseJson(data);
3048 $.each(data.containerContents, function(key, value){
3049 if (value.objId == json.objId && value.playlist) {
3050 $(".subHeader", "#browseContents").after('<a class="actionbtnlg floatL playlistButton" onmousedown="onButtonMouseDown(this)" onmouseup="onButtonMouseUp(this)" href="' + value.playlist + '" target="_blank"><span class="actionbtn_l actionbtn_ico"><img class="icolg icolg_play" src="/webconfig/spacer.gif" /></span><span class="actionbtn_l"></span><span class="actionbtn_c"> ' + getString("play") + ' </span><span class="actionbtn_r"></span></a><div class="clear"></div>');
3051 return false;
3052 }
3053 });
3054 });
3055 }
3056 }
3057 else {
3058 var emptyMessage = "";
3059 switch ($(".active", "#nav").attr("id")) {
3060 case "0$1":
3061 emptyMessage = getString("nomusic");
3062 break;
3063 case "0$2":
3064 emptyMessage = getString("nophotos");
3065 break;
3066 case "0$3":
3067 emptyMessage = getString("novideos");
3068 break;
3069 }
3070 html += emptyMessage;
3071 }
3072 $("#browseContents").html(html);
3073
3074 var numPages = Math.ceil(numItems / pageCount);
3075 var currentPageNum = (startItem / mediaBrowsePageCount) + 1;
3076 var pagesHtml = "<div>";
3077 if (numPages > 1) {
3078 var lowerDisplay;
3079 var upperDisplay;
3080 //If there are 9 or less pages, display all of them.
3081 if (numPages <= 9) {
3082 lowerDisplay = 1;
3083 upperDisplay = numPages;
3084 }
3085 else {
3086 //Otherwise, display the current page, plus three pages in each direction.
3087 lowerDisplay = currentPageNum - 3;
3088 upperDisplay = currentPageNum + 3;
3089 var numTooLow = 0;
3090 var numTooHigh = 0;
3091 //Determine if lowerDisplay is 0 or lower, since we can't display that.
3092 if (lowerDisplay < 1) {
3093 for (var i = 0; i < Math.abs(1 - lowerDisplay); i++) {
3094 if (upperDisplay + 1 <= numPages) {
3095 numTooLow++;
3096 }
3097 else {
3098 return;
3099 }
3100 }
3101 }
3102 //Determine if upperDisplay is greater than the number of pages, since we can't display that.
3103 if (upperDisplay > numPages) {
3104 for (var i = 0; i < (upperDisplay - numPages); i++) {
3105 if (lowerDisplay - 1 >= 1) {
3106 numTooHigh++;
3107 }
3108 else {
3109 return;
3110 }
3111 }
3112 }
3113 //If either display threshold is too low or too high, increment the other so 7 pages are always shown.
3114 if (numTooLow) {
3115 lowerDisplay += numTooLow;
3116 upperDisplay += numTooLow;
3117 }
3118 if (numTooHigh) {
3119 lowerDisplay -= numTooHigh;
3120 upperDisplay -= numTooHigh;
3121 }
3122 }
3123 var previousPagesHtml = "";
3124 //If page 1 and page 2 aren't already displayed, show them.
3125 if (1 < lowerDisplay) {
3126 previousPagesHtml += makePaginationLink(id, 0 * pageCount, numItems, 1)
3127 }
3128 if (2 < lowerDisplay) {
3129 previousPagesHtml += makePaginationLink(id, 1 * pageCount, numItems, 2)
3130 }
3131 if (previousPagesHtml) {
3132 previousPagesHtml += " ... ";
3133 pagesHtml += previousPagesHtml;
3134 }
3135 //Show a link for each page in the range of lowerDisplay to upperDisplay.
3136 for (var i = lowerDisplay - 1; i < upperDisplay; i++) {
3137 if ((i * pageCount) == startItem) {
3138 pagesHtml += '<span>' + (i + 1) + '</span> ';
3139 }
3140 else {
3141 pagesHtml += makePaginationLink(id, i * pageCount, numItems, i + 1)
3142 }
3143 }
3144 var subsequentPagesHtml = "";
3145 //If the last two pages aren't already displayed, show them.
3146 if (numPages - 1 > upperDisplay) {
3147 subsequentPagesHtml += makePaginationLink(id, (numPages - 2) * pageCount, numItems, numPages - 1)
3148 }
3149 if (numPages > upperDisplay) {
3150 subsequentPagesHtml += makePaginationLink(id, (numPages - 1) * pageCount, numItems, numPages)
3151 }
3152 if (subsequentPagesHtml) {
3153 subsequentPagesHtml = " ... " + subsequentPagesHtml;
3154 pagesHtml += subsequentPagesHtml;
3155 }
3156 }
3157 pagesHtml += "</div>";
3158
3159 //Display some text to indicate the current range of items and the total number.
3160 if (numPages > 1) {
3161 var endItem = (parseInt(startItem) + pageCount)
3162 pagesHtml += "<div>" + (parseInt(startItem) + 1) + " - " + ((endItem < numItems) ? endItem : numItems) + " " + getString("of") + " " + numItems + "</div>";
3163 }
3164 $(".browsePages").html(pagesHtml);
3165 //To provide a better user experience, when a media browse link is clicked, scroll back to the top of the page.
3166 window.scrollTo(0, 0);
3167
3168 var breadcrumb = $(".breadcrumb");
3169 var lastBreadcrumb = $(".breadcrumb > .breadcrumbItem:last");
3170 //If there's no breadcrumb yet, add one.
3171 if (lastBreadcrumb.length == 0) {
3172 //If an element can be found that matches with the selected element, add inactive text to the breadcrumb.
3173 if ($("a", clickedElement).length > 0) {
3174 breadcrumb.append('<span class="breadcrumbItem" pathid="' + id + '" numitems="' + numItems + '">' + $("a", clickedElement).text() + '</span>');
3175 lastBreadcrumb = $(".breadcrumb > .breadcrumbItem:last");
3176 }
3177 //Otherwise, the user has refreshed the page and a fresh breadcrumb needs to be generated.
3178 else {
3179 var idPieces = id.split("$");
3180 //Offset indicates the number of $ + 1 in the id string. 4 is used because that produces an id
3181 //that matches with the left nav items (e.g. 0$3/0$3$30 for By Date).
3182 var offset = 3;
3183 var levels = [];
3184 var levelsHtml = {};
3185 //Beginning with a left nav id, successively reconstruct id strings out of the full id by
3186 //appending additional levels (marked by the $).
3187 for (var i = 0; i < idPieces.length - offset + 1; i++) {
3188 var newId = idPieces.slice(0, offset + i);
3189 var newIdStr = newId.join("$");
3190 levels.push(newIdStr);
3191 }
3192 var numLevels = levels.length;
3193 var levelsFetched = 0;
3194 //For each of the reconstructed level strings, build breadcrumb html (a link except for the last item).
3195 //Store the HTML by id.
3196 $.each(levels, function(i, value){
3197 makeGetRequest("/json/feed" + value, {}, function(data){
3198 data = parseJson(data);
3199 //For the first level, highlight the matching left-nav item.
3200 if (i == 0) {
3201 $("#" + value.replace(/\$/g, "\\$").replace(/\//g, "\\/")).addClass("current");
3202 }
3203 if (i != levels.length - 1) {
3204 levelsHtml[value] = '<span class="breadcrumbWrapper" pathid="' + value + '"><a onclick="navigateTo(\'id=' + value + '&startPage=0&count=' + data.containerItemCount + '\')"><span class="breadcrumbItem" pathid="' + value + '" numitems="' + data.containerItemCount + '">' + data.containerTitle + '</span></a> / </span>';
3205 }
3206 else {
3207 levelsHtml[value] = '<span class="breadcrumbItem" pathid="' + value + '" numitems="' + data.containerItemCount + '">' + data.containerTitle + '</span>';
3208 }
3209 levelsFetched++;
3210 //Once breadcrumb data has been fetched for all levels, insert it into the container.
3211 if (levelsFetched == numLevels) {
3212 $.each(levels, function(j, level){
3213 breadcrumb.append(levelsHtml[level]);
3214 });
3215 }
3216 });
3217 });
3218 }
3219 }
3220 else {
3221 //Count the number of $ to determine how many levels deep the navigation is.
3222 // for myTwonky count the M too.
3223 var lb = lastBreadcrumb.attr("pathid");
3224 if (lb.length > 0) lb = lb.replace(/M/g, "$");
3225 var numBreadcrumbLevels = (lb) ? (lb.split("$").length - 1) : (0);
3226 var id2 = id;
3227 id2 = id2.replace(/M/g, "$");
3228 var numIdLevels = id2.split("$").length - 1;
3229
3230 //Compare the number of levels in the breadcrumb to the number of levels of the id. If the id has more levels,
3231 //add it to the breadcrumb and add a hyperlink to the previous breadcrumb item.
3232 if (lastBreadcrumb.length > 0 && numIdLevels > numBreadcrumbLevels && lastBreadcrumb.attr("pathid")) {
3233 if (lastBreadcrumb.length > 0) {
3234 lastBreadcrumb.wrap('<span class="breadcrumbWrapper" pathid="' + lastBreadcrumb.attr("pathid") + '"><a onclick="navigateTo(\'id=' + lastBreadcrumb.attr("pathid") + '&startPage=0&count=' + lastBreadcrumb.attr("numItems") + '\')"></a> / </span>');
3235 }
3236 breadcrumb.append('<span class="breadcrumbItem" pathid="' + id + '" numitems="' + numItems + '">' + clickedElementText + '</span>');
3237 }
3238 //Otherwise, find the breadcrumb item with the same level as the new item. Replace the old item with the new one
3239 //at the same level and remove the rest of the breadcrumb after that.
3240 else {
3241 var collection = ($(".breadcrumbWrapper", ".breadcrumb").length > 0) ? ($(".breadcrumbWrapper", ".breadcrumb")) : ($(".breadcrumb > .breadcrumbItem:last"));
3242 $.each(collection, function(i, element){
3243 element = $(element);
3244 var elementBreadcrumbLevels = 0;
3245 if (element.attr("pathid")) {
3246 var p = element.attr("pathid");
3247 p = p.replace(/M/g, "$");
3248 elementBreadcrumbLevels = p.split("$").length - 1;
3249 }
3250 if (elementBreadcrumbLevels == numIdLevels) {
3251 var newText = (clickedElement.length > 0) ? ($("a", clickedElement).text()) : ($(".breadcrumbItem[pathid=" + cleanedId + "]", ".breadcrumb").text());
3252 element.nextAll().remove();
3253 element.attr("pathid", id);
3254 element.replaceWith('<span class="breadcrumbItem" pathid="' + id + '" numitems="' + numItems + '">' + newText + '</span>');
3255 hideLoadingGraphic();
3256 return false;
3257 }
3258 });
3259 }
3260 }
3261 // start a second http-request if all containers has 0 items
3262 if ((nullNodes == nodes) && nodes > 0 && !reloadContent) {
3263 var str = "reloadMediaContent(\'"+id+"\', \'"+startItem+"\', \'"+numItems+"\')";
3264 var timerReload = setTimeout(str,500);
3265 } else hideLoadingGraphic();
3266 });
3267}
3268function reloadMediaContent(id, startItem, numItems) {
3269 loadMediaContents(id, startItem, numItems, true);
3270}
3271
3272function onMediaNodeClicked(node, count){
3273 var id = node.id;
3274 navigateTo("id=" + id + "&startPage=0&count=" + count);
3275}
3276
3277//Get the HTML for a pagination link.
3278//id: The id of the media node to browse to.
3279//pageCount: The start item for the media browse call.
3280//numItems: The number of child items.
3281//text: The text of the link.
3282function makePaginationLink(id, pageCount, numItems, text){
3283 return '<a onclick="navigateTo(\'id=' + id + '&startPage=' + pageCount + '&count=' + numItems + '\')">' + text + '</a> '
3284}
3285
3286var videoDefaultImg = "/webconfig/gen_video_100.png";
3287var videoWidth = 100;
3288var videoHeight = 100;
3289var musicDefaultImg = "/webconfig/gen_music_40.png";
3290var musicWidth = 40;
3291var musicHeight = 40;
3292var photoDefaultImg = "/webconfig/gen_photo_100.png";
3293var photoWidth = 100;
3294var photoHeight = 100;
3295var videoFolderDefaultImg = "/webconfig/gen_folder_video.png";
3296var musicFolderDefaultImg = "/webconfig/gen_folder_music_110.png";
3297var photoFolderDefaultImg = "/webconfig/gen_folder_photo.png";
3298var folderWidth = 185;
3299var musicFolderWidth = 110;
3300var folderHeight = 110;
3301
3302//Given a thumbnail tag, separate the link from it by parsing it out from between the quotes of the url attribute.
3303//thumbnailValue: The <media:thumbnail> tag to parse.
3304//mediaType: The type of media that a thumbnail is being retrieved for.
3305//isFolder: Default false. Indicates whether the thumbnail is for a folder display or for media display.
3306//originalWidth: Populated only for Photos. The original width of the image.
3307//originalHeight: Populated only for Photos. The original height of the image.
3308function getThumbnailLink(thumbnailValue, mediaType, isFolder, originalWidth, originalHeight){
3309 var thumbnailObj = {
3310 "link": "",
3311 "scale": "",
3312 "clip": ""
3313 };
3314 if (thumbnailValue) {
3315 var firstQuote = thumbnailValue.indexOf('"');
3316 var endChar = (thumbnailValue.lastIndexOf('?') > -1) ? (thumbnailValue.lastIndexOf('?')) : (thumbnailValue.length);
3317 thumbnailObj.link = thumbnailValue.substring(firstQuote + 1, endChar);
3318 if (isFolder) {
3319 if (mediaType == "P") {
3320 //thumbnailObj.link = photoFolderDefaultImg;
3321 var scaleData = getImageScale(originalWidth, originalHeight, folderWidth, folderHeight);
3322 thumbnailObj.scale = "?scale=" + scaleData.x + "x" + scaleData.y;
3323 thumbnailObj.clip = scaleData.clip;
3324 }
3325 else
3326 if (mediaType == "M") {
3327 //thumbnailObj.link = musicFolderDefaultImg;
3328 thumbnailObj.scale = "?scale=" + musicFolderWidth + "x" + folderHeight;
3329 }
3330 else {
3331 //thumbnailObj.link = videoFolderDefaultImg;
3332 thumbnailObj.scale = "?scale=" + folderWidth + "x" + folderHeight;
3333 }
3334 }
3335 else {
3336 switch (mediaType) {
3337 case "V":
3338 thumbnailObj.scale = "?scale=" + videoWidth + "x" + videoHeight;
3339 break;
3340 case "M":
3341 thumbnailObj.scale = "?scale=" + musicWidth + "x" + musicHeight;
3342 break;
3343 case "P":
3344 var scaleData = getImageScale(originalWidth, originalHeight, photoWidth, photoHeight);
3345 thumbnailObj.scale = "?scale=" + scaleData.x + "x" + scaleData.y;
3346 thumbnailObj.clip = scaleData.clip;
3347 break;
3348 }
3349 }
3350 return thumbnailObj;
3351 }
3352}
3353
3354//Recursively navigate into a node until the first leaf node is identified, then set the thumbnail of that node
3355//to an image.
3356//image: The image to be updated when the thumbnail is identified.
3357//id: The id to navigate into.
3358function getFolderThumbnail(image, id){
3359 makeGetRequest("/json/feed/" + id, {
3360 "start": 0,
3361 "count": 1
3362 }, function(response){
3363 var json = parseJson(response);
3364 if (json.containerContents.length == 0) {
3365 return;
3366 }
3367 switch (json.containerContents[0].nodeType) {
3368 case "branch":
3369 getFolderThumbnail(image, json.containerContents[0].objId)
3370 break;
3371 case "leaf":
3372 var width;
3373 var height;
3374 var thumbnailData;
3375 if (json.containerContents[0].objType == "P") {
3376 var resolutionPieces = json.containerContents[0].resolution.split("x");
3377 width = parseInt(resolutionPieces[0]);
3378 height = parseInt(resolutionPieces[1]);
3379 thumbnailData = getThumbnailLink(json.containerContents[0].thumbnail, json.containerContents[0].objType, true, width, height);
3380 }
3381 else {
3382 thumbnailData = getThumbnailLink(json.containerContents[0].thumbnail, json.containerContents[0].objType, true);
3383 }
3384 if (thumbnailData.link == videoDefaultImg || thumbnailData.link == musicDefaultImg || thumbnailData.link == photoDefaultImg) {
3385 loadDefaultThumbnail(image, json.containerContents[0].objType);
3386 }
3387 else {
3388 //thumbnail = '<img class="folderThumbnail" src="' + data.thumbnail + '" onerror="loadDefaultThumbnail($(this), \'' + objType + '\')" />';
3389 //image.replaceWith('<img src="' + thumbnailData.link + thumbnailData.scale + '" onerror="loadDefaultThumbnail($(this), \'' + json.containerContents[0].objType + '\')" style="' + thumbnailData.clip + '"/>');
3390 image.replaceWith('<img class="folderThumbnail" src="' + thumbnailData.link + thumbnailData.scale + '" onerror="loadDefaultThumbnail($(this), \'' + json.containerContents[0].objType + '\')" style="' + thumbnailData.clip + '"/>');
3391 }
3392 break;
3393 }
3394 });
3395}
3396
3397//Load the default thumbnail for an image if the one specified in the media browse API can't be successfully loaded.
3398//image: The image to change the src of.
3399//mediaType: The media type of the node (video, music, photo). Used to determine which image to display.
3400function loadDefaultThumbnail(image, mediaType){
3401 var imagePath;
3402 //If the image is a child of an element with the byFolderContainer class, use the larger image.
3403 //Otherwise, use the smaller image for that content type.
3404 switch (mediaType) {
3405 case "V":
3406 imagePath = (image.parent().is(".byFolderContainer")) ? (videoFolderDefaultImg) : (videoDefaultImg);
3407 break;
3408 case "M":
3409 imagePath = (image.parent().is(".byFolderContainer")) ? (musicFolderDefaultImg) : (musicDefaultImg);
3410 break;
3411 case "P":
3412 imagePath = (image.parent().is(".byFolderContainer")) ? (photoFolderDefaultImg) : (photoDefaultImg);
3413 break;
3414 }
3415 image.attr("src", imagePath);
3416}
3417
3418//Given the original dimensions of an image, scale it to a new size and preserve the aspect ratio.
3419//originalWidth, originalHeight: The dimensions of the image to be scaled.
3420//desiredWidth, desiredHeight: The dimensions that the image should be scaled to.
3421function getImageScale(originalWidth, originalHeight, desiredWidth, desiredHeight){
3422 var scaleObj = {
3423 "x": "",
3424 "y": "",
3425 "clip": ""
3426 };
3427 //If the image is a landscape (the width is greater than the height), assign the largest dimension to the width.
3428 if (originalWidth >= originalHeight) {
3429 var ratio = originalHeight / originalWidth;
3430 var scaledHeight = ratio * desiredWidth;
3431 if (desiredHeight > scaledHeight) {
3432 var newRatio = desiredHeight / scaledHeight;
3433 scaleObj.x = Math.round(newRatio * desiredWidth);
3434 scaleObj.y = Math.round(desiredHeight);
3435 }
3436 else {
3437 scaleObj.x = Math.round(desiredWidth);
3438 scaleObj.y = Math.round(ratio * desiredWidth);
3439 }
3440 }
3441 //Do the reverse if the image is a portrait (height is greater than width).
3442 else {
3443 var ratio = originalWidth / originalHeight;
3444 var scaledWidth = ratio * desiredHeight;
3445 if (desiredWidth > scaledWidth) {
3446 var newRatio = desiredWidth / scaledWidth;
3447 scaleObj.x = Math.round(desiredWidth);
3448 scaleObj.y = Math.round(newRatio * desiredHeight);
3449 }
3450 else {
3451 scaleObj.x = Math.round(ratio * desiredHeight);
3452 scaleObj.y = Math.round(desiredHeight);
3453 }
3454 }
3455 var widthOffset = 0;
3456 var heightOffset = 0;
3457 if (scaleObj.x > desiredWidth) {
3458 widthOffset = (parseInt(scaleObj.x) - desiredWidth) / 2;
3459 }
3460 if (scaleObj.y > desiredHeight) {
3461 heightOffset = (parseInt(scaleObj.y) - desiredHeight) / 2;
3462 }
3463 scaleObj.clip = "clip: rect(" + heightOffset + "px " + (scaleObj.x - widthOffset) + "px " + (scaleObj.y - heightOffset) + "px " + widthOffset + "px); left: -" + widthOffset + "px; top: -" + heightOffset + "px;";
3464 return scaleObj;
3465}
3466
3467function toggleContainer(clickedButton)
3468{
3469 var parent = clickedButton.parents(".boxHeader");
3470 var toggleElement = $(parent).next();
3471 var elementID = clickedButton.attr("id");
3472 if (toggleElement.css("display") == "none") {
3473 toggleElement.show();
3474 $(".toggleText", clickedButton).text(getString("hide"));
3475 clickedButton.removeClass("hidden");
3476 clickedButton.addClass("showing");
3477 document.cookie = elementID + "=show;";
3478 }
3479 else {
3480 toggleElement.hide();
3481 $(".toggleText", clickedButton).text(getString("show"));
3482 clickedButton.removeClass("showing");
3483 clickedButton.addClass("hidden");
3484 document.cookie = elementID + "=hide;";
3485 }
3486}
3487
3488
3489// ------------------------------------------
3490// synchronization (WebDav server)
3491// ------------------------------------------
3492var shares_alias_array = new Array();
3493var shares_path_array = new Array();
3494var shares_user_array = new Array();
3495var shares_rights_array = new Array();
3496var user_array = new Array();
3497var rights_user_array = new Array();
3498var rights_share_array = new Array();
3499var rights_mode_array = new Array();
3500
3501var values_loaded=0;
3502var reserve_user_name = "Any";
3503
3504
3505function loadSynchronization(){
3506// getMetaInfo();
3507 populateSettingsNav();
3508 makeGetRequest("/webconfig/synchronization.htm", {}, function(response){
3509 var responseHtml = $(response);
3510 replaceStrings(responseHtml);
3511 showToggleButtons(responseHtml);
3512 $("#userslist",responseHtml).html(getUserList());
3513 $("#sharedlist",responseHtml).html(getShareList());
3514 $(".serverSettingsContentWrapper").html(responseHtml);
3515 highlightNav($("#nav_synchronization"));
3516 });
3517}
3518
3519function getMetaInfo() {
3520 if (!getServerDetails()) return;
3521 var rawdata = getServerDetails().responseText;
3522 var lines = rawdata.split("\n");
3523 server_name = lines[0];
3524 server_port = lines[1];
3525 server_version = lines[2];
3526 //globalLanguage = lines[3]
3527 server_start_time = lines[4];
3528 server_build_date = lines[5];
3529}
3530function getServerDetails() {
3531 return loadXMLDoc("/get_server_info","");
3532}
3533
3534function showDialog(msg, handler){
3535 showDialogOverlay(function(){
3536 return msg;
3537 }, {}, {
3538 1: {
3539 text: getString("ok"),
3540 onclick: "hideDialogOverlay(); " + handler
3541 }
3542 });
3543}
3544function confirmDialog(msg,handler) {
3545 showDialogOverlay(function(){
3546 return msg;
3547 }, null, {
3548 1: {
3549 text: getString("ok"),
3550 onclick: handler
3551 },
3552 2: {
3553 text: getString("cancel"),
3554 onclick: "hideDialogOverlay()"
3555 }
3556 });
3557}
3558
3559// status subheader: reload
3560function reloadConfig() {
3561 var request = loadXMLDoc("/reload","");
3562 if (request.status != 200) {
3563 showDialog(getString("reload_failed"));
3564 } else {
3565 window.location.reload();
3566 }
3567 highlightNav($("#nav_synchronization"));
3568}
3569
3570function getUserList() {
3571 var i;
3572 var strOut = "";
3573 var numUsers = 0;
3574 if (!values_loaded) loadAll();
3575 numUsers = user_array.length
3576
3577 if (numUsers <= 0) {
3578 strOut += "<font color=\"red\">" + getString("no_user_added") + "</font>";
3579 } else {
3580 strOut += "<select class=\"selectUserInput floatL\" id=\"user\">";
3581 for (i=0;i<numUsers;i++) {
3582 strOut += "<option "+((i==0)?"selected ":"")+"value=\""+user_array[i]+"\">"+user_array[i]+"</option>";
3583 }
3584 strOut += "</select>";
3585 strOut += "<a class=\"actionbtn floatL\" onmousedown=\"onButtonMouseDown(this)\" onmouseup=\"onButtonMouseUp(this)\" onclick=\"deleteUser();\"> <span class=\"actionbtn_l\"></span><span class=\"actionbtn_c\">"+getString("delete")+"</span><span class=\"actionbtn_r\"></span></a>";
3586 strOut += "<div class=\"smallServerContentSpacer\"></div>";
3587 }
3588 return strOut;
3589}
3590function loadAll() {
3591 var rawdata=getShares().responseText;
3592 if (rawdata == "failed") return;
3593 var lines=rawdata.split("\n");
3594 var len = parseInt(lines.length/4);
3595 shares_alias_array = new Array(len);
3596 shares_path_array = new Array(len);
3597 shares_user_array = new Array(len);
3598 shares_rights_array = new Array(len);
3599 for (i=0;i<len;i++) {
3600 shares_alias_array[i]=lines[4*i];
3601 shares_path_array[i]=lines[4*i+1];
3602 shares_user_array[i]=lines[4*i+2];
3603 shares_rights_array[i]=lines[4*i+3];
3604 }
3605
3606 rawdata = getUsers().responseText;
3607 if (rawdata == "failed") return;
3608 lines = rawdata.split("\n");
3609 len = lines.length-1;
3610 user_array = new Array(len);
3611 for (i=0;i<len;i++) {
3612 user_array[i]=lines[i];
3613 }
3614
3615 rawdata=getRights().responseText;
3616 if (rawdata == "failed") return;
3617 lines=rawdata.split("\n");
3618 len = parseInt(lines.length/3);
3619 rights_user_array = new Array(len);
3620 rights_share_array = new Array(len);
3621 rights_mode_array = new Array(len);
3622 for (i=0;i<len;i++) {
3623 rights_user_array[i]=lines[3*i];
3624 rights_share_array[i]=lines[3*i+1];
3625 rights_mode_array[i]=lines[3*i+2];
3626 }
3627 values_loaded=1;
3628}
3629function getShares() {
3630 var request = loadXMLDoc("/get_shares","");
3631 if (request.status != 200) {
3632 showDialog(getString("dialog_server_req_failed"));
3633 request.responseText = "failed";
3634 }
3635 return request;
3636}
3637function getUsers() {
3638 var request = loadXMLDoc("/get_users","");
3639 if (request.status != 200) {
3640 showDialog(getString("dialog_server_req_failed"));
3641 request.responseText = "failed";
3642 }
3643 return request;
3644}
3645function getRights() {
3646 var request = loadXMLDoc("/get_rights","");
3647 if (request.status != 200) {
3648 showDialog(getString("dialog_server_req_failed"));
3649 request.responseText = "failed";
3650 }
3651 return request;
3652}
3653
3654
3655// delete and add users
3656function deleteUser() {
3657 var user = $("#user").val();
3658 var msg = formatString(getString("dialog_delete_user"),[user,user]);
3659 confirmDialog(msg,"confirmDeleteUser()");
3660}
3661function confirmDeleteUser() {
3662 var user = $("#user").val();
3663 user = encodeURIComponent(user);
3664 var request = loadXMLDoc("/delete_user?user="+user,"");
3665 if (request.status != 200) {
3666 showDialog(getString("dialog_server_req_failed"));
3667 } else {
3668 updateSharesForUser(user);
3669 window.location.reload();
3670 highlightNav($("#nav_synchronization"));
3671 }
3672}
3673function updateSharesForUser(user) {
3674 var len = shares_alias_array.length;
3675 var i = 0;
3676 for (i=0;i<len;i++) {
3677 if (shares_user_array[i].length <= 0) continue;
3678 if (shares_user_array[i] != user) continue;
3679 removeRight(shares_alias_array[i]);
3680 }
3681}
3682function removeRight(alias) {
3683 alias = encodeURIComponent(alias);
3684 var request = loadXMLDoc("/remove_right?alias="+alias,"");
3685 if (request.status != 200) {
3686 showDialog(getString("dialog_server_req_failed"));
3687 return 0;
3688 }
3689 return 1;
3690}
3691function highlightText(field) {
3692 if(field!=null) {
3693 field.focus();
3694 field.select();
3695 }
3696}
3697
3698function handleKeyPress(event) {
3699 // escape key
3700 if (event.keyCode==27) {
3701 hideDialogOverlay();
3702 }
3703}
3704function promptUserDialog(msg, handler, ispassword){
3705var inputtype="text";
3706var inputvalue=msg;
3707 if(ispassword) {
3708 inputtype = "password";
3709 inputvalue = "";
3710 }
3711 showDialogOverlay(function(){
3712 return "<div>" + msg + "</div>"
3713 + "<input type=\""+inputtype+"\" id=\"promptInput\" align='center' type=\"text\" value=\"" + inputvalue + "\" onkeyup=\"handleKeyPress(event)\" onclick=\"highlightText(this)\"/>";
3714 }, null, {
3715 1: {
3716 text: getString("ok"),
3717 onclick: handler
3718 },
3719 2: {
3720 text: getString("cancel"),
3721 onclick: "hideDialogOverlay()"
3722 }
3723 });
3724}
3725function addUser() {
3726 promptUserDialog(getString("enter_valid_un"),"userEntered()", false);
3727}
3728var validUser;
3729function userEntered(){
3730 var user = $("#promptInput").val();
3731 if (user == getString("enter_valid_un")) {
3732 user = '';
3733 } else if (user.toLowerCase().replace(/\s/g, "") == reserve_user_name.toLowerCase()) {
3734 showDialog(formatString(getString("dialog_reserve_user_name"),[reserve_user_name]));
3735 return;
3736 }
3737 if (user.length > 0) {
3738 validUser = encodeURIComponent(user);
3739 promptUserDialog(getString("enter_valid_pw"),"passwordEntered()", true);
3740 } else {
3741 showDialog(getString("dialog_enter_valid_un"));
3742 }
3743}
3744function passwordEntered () {
3745 var pw = $("#promptInput").val();
3746 if (pw == getString("enter_valid_pw")) {
3747 pw = '';
3748 }
3749 if (pw.length > 0) {
3750 pw = encodeURIComponent(pw);
3751 var request = loadXMLDoc("/add_user?user=" + validUser + "&pass=" +pw,"");
3752 if (request.status != 200) {
3753 showDialog(getString("dialog_server_req_failed"));
3754 } else {
3755 showDialog(getString("dialog_user_add_success"),"window.location.reload()");
3756 }
3757 } else {
3758 showDialog(getString("dialog_enter_valid_pw"));
3759 }
3760}
3761
3762
3763// SHARES
3764function getShareList() {
3765 folderBrowseDialogMsg = 2;
3766 var i;
3767 var strOut = "";
3768 var newlink = "";
3769 if (!values_loaded) loadAll();
3770 var numSharesPresent = shares_alias_array.length;
3771
3772 if (numSharesPresent <= 0) {
3773 strOut += "<font color=\"red\">" + getString("no_syncfolders") + "</font>";
3774 }
3775 else {
3776 // table columns: share name, share path, user, share rights, button delete share, button view share
3777 strOut += "<table class='syncTable'><tr>"
3778 + "<th>" + getString("syncfolder_name") + "</th>"
3779 + "<th>" + getString("local_folder") + "</th>"
3780 + "<th style=\"text-align:center\">" + getString("username") + "</th>"
3781 + "<th style=\"text-align:center\">" + getString("rights") + "</th>"
3782 + "<th></th>"
3783 + "<th></th>"
3784 + "</tr>";
3785 var uname = reserve_user_name;
3786 // Get the existing shares
3787 for (i=0;i<shares_alias_array.length;i++) {
3788 if (shares_user_array[i].length > 0)
3789 uname = shares_user_array[i];
3790 else
3791 uname = reserve_user_name;
3792 strOut += "<tr><td><input class=\"floatL\" type=\"text\" readonly=\"readonly\" disabled=\"true\" value=\"" + shares_alias_array[i] + "\"/>"
3793 + "</td><td>"
3794 + "<input class=\"longInput floatL\" type=\"text\" readonly=\"readonly\" disabled=\"true\" value=\"" + shares_path_array[i] + "\"/>"
3795 + "</td><td>"
3796 + "<input class=\"webdavuser floatL\" style=\"text-align:center\" type=\"text\" readonly=\"readonly\" disabled=\"true\" value=\"" + uname + "\"/>"
3797 + "</td><td>"
3798 + "<input class=\"webdavrights floatL\" style=\"text-align:center\" type=\"text\" readonly=\"readonly\" disabled=\"true\" value=\"" + shares_rights_array[i] + "\"/>"
3799 + "</td><td>"
3800 + "<a style=\"width:100%\" class=\"actionbtn floatL\" onmousedown=\"onButtonMouseDown(this)\" onmouseup=\"onButtonMouseUp(this)\" onClick=\"deleteShare('"+ shares_alias_array[i] +"');\">"
3801 + "<span class=\"actionbtn_l\"></span><span class=\"actionbtn_c\">" + getString('delete') + "</span><span class=\"actionbtn_r\"></span>"
3802 + "</a></td>"
3803 + "<td>"
3804 + "<a style=\"width:100%\" class=\"actionbtn floatL\" onmousedown=\"onButtonMouseDown(this)\" onmouseup=\"onButtonMouseUp(this)\" onClick=\"openShare(" + i + ");\">"
3805 + "<span class=\"actionbtn_l\"></span><span class=\"actionbtn_c\">" + getString('view') + "</span><span class=\"actionbtn_r\"></span>"
3806 + "</a></td>"
3807 + "</tr>";
3808 }
3809 strOut += "</table>";
3810 }
3811 strOut += "<div class=\"smallServerContentSpacer\"/>"
3812 strOut += "<div><b><span class=\"title\">" + getString("syncfolder_new_share_info") + "</span></div></b>";
3813 // table columns: share name, sahre path, button browse path, user, share rights
3814 strOut += "<table class='syncTable'><tr>"
3815 + "<th>" + getString("syncfolder_name") + "</th>"
3816 + "<th>" + getString("local_folder") + "</th>"
3817 + "<th></th>"
3818 + "<th>" + getString("username") + "</th>"
3819 + "<th>" + getString("rights") + "</th>"
3820 + "</tr>";
3821 strOut += "<tr><td>"
3822 + "<input id=\"shareAlias\" class=\"pathInput floatL\" type=\"text\" value=\"\" />"
3823 + "</td><td>"
3824 + "<input id=\"pathInput1\" readonly=\"readonly\" class=\"longInput floatL\" type=\"text\" value=\"\"/>"
3825 + "</td><td>"
3826 + "<a style=\"width:100%\" class=\"actionbtn floatL\" onmousedown=\"onButtonMouseDown(this)\" onmouseup=\"onButtonMouseUp(this)\" onClick=\"showFolderBrowse('1');\">"
3827 + "<span class=\"actionbtn_l\"></span><span class=\"actionbtn_c\">" + getString("browse") + "</span><span class=\"actionbtn_r\"></span>"
3828 + "</a></td><td>"
3829 + "<select class=\"selectUserInput floatL\" id=\"userInput\" onchange=\"updateRight(this.value)\">";
3830 for (i = 0; i < user_array.length; i++) {
3831 if (i == 0) strOut += "<option selected=\"selected\" value=\"" + user_array[i] + "\">" + user_array[i] + "</option>";
3832 else strOut += "<option value=\"" + user_array[i] + "\">" + user_array[i] + "</option>";
3833 }
3834 strOut += "<option value=\"" + reserve_user_name + "\">" + getString("any") + "</option></select>"
3835 + "</td><td>";
3836 if (user_array.length == 0)
3837 strOut += "<select class=\"selectRightsInput floatL\" id=\"right\">"
3838 + "<option selected=\"selected\" value=\"r/w\">r/w</option>"
3839 + "</select></td></tr>";
3840 else
3841 strOut += "<select class=\"selectRightsInput floatL\" id=\"right\">"
3842 + "<option selected=\"selected\" value=\"r/w\">r/w</option>"
3843 + "<option value=\"r/o\">r/o</option>"
3844 + "</select></td></tr>";
3845 strOut += "</table>";
3846 strOut += "<div class=\"verySmallServerContentSpacer\"></div>";
3847 return strOut;
3848}
3849function updateRight(val) {
3850 var elem = document.getElementById("right");
3851 elem.options.length = 0;
3852 if (val == reserve_user_name) {
3853 elem.options[0] = new Option("r/w", "r/w",true);
3854 } else {
3855 elem.options[0] = new Option("r/w", "r/w",true);
3856 elem.options[1] = new Option("r/o", "r/o");
3857 }
3858}
3859function getShareUrl(idx)
3860{
3861 var url = shares_alias_array[idx];
3862 var lines = url.split("/");
3863 if (lines.length > 1) {
3864 url = "/" + encodeURIComponent(lines[1]);
3865 }
3866 if (shares_alias_array[idx] != "/") {
3867 url += "/";
3868 }
3869 return statusData["syncurl"] + url;
3870}
3871function openShare(idx) {
3872 window.open(getShareUrl(idx));
3873}
3874
3875// add shares and rights, delete shares
3876function addShareAndRights() {
3877 var alias = $("#shareAlias").val();
3878 var folder = $("#pathInput1").val();
3879 var user = $("#userInput").val();
3880 var right = $("#right").val();
3881 var result = 0;
3882 var validRights = 0;
3883 var enableAuth = 1;
3884 if (user.toLowerCase().replace(/\s/g, "") == reserve_user_name.toLowerCase()) {
3885 enableAuth = 0;
3886 }
3887
3888 result = addShare(alias, folder)
3889 if (1 == result) {
3890 if (enableAuth == 1) {
3891 result = addRight(alias, user, right);
3892 } else {
3893 result = removeRight(alias);
3894 }
3895 if (1 == result) {
3896 showDialog(getString("dialog_share_added_success"),"window.location.reload()");
3897 }
3898 }
3899}
3900function addRight(alias,user,mode) {
3901 if (alias.charAt(0) != '/')
3902 alias = '/' + alias;
3903 alias = encodeURIComponent(alias);
3904 user = encodeURIComponent(user);
3905 var request = loadXMLDoc("/rpc/webdav/add_right?alias="+alias+"&user="+user+"&mode="+mode,"");
3906 if (request.status != 200) {
3907 showDialog(getString("dialog_server_req_failed"));
3908 return 0;
3909 }
3910 return 1;
3911}
3912function addShare(alias,path) {
3913 if (alias==null || alias=="") {
3914 showDialog(getString("dialog_enter_alias_name"));
3915 return 0;
3916 }
3917
3918 var lines = alias.split("\\");
3919 if (lines.length > 1) {
3920 showDialog(formatString(getString("dialog_invalid_share_name_2"),["\\"]));
3921 return 0;
3922 }
3923
3924 var lines = alias.split("/");
3925 if (lines.length > 2) {
3926 showDialog(formatString(getString("dialog_invalid_share_name_1"),["/","/","/","/","/"]));
3927 return 0;
3928 }
3929
3930 if (path==null || path=="") {
3931 showDialog(getString("dialog_enter_share_dir"));
3932 return 0;
3933 }
3934
3935 if (alias.charAt(0) != '/')
3936 alias = '/' + alias;
3937
3938 alias = encodeURIComponent(alias);
3939 path = encodeURIComponent(path);
3940
3941 var request = loadXMLDoc("/add_share?alias="+alias+"&path="+path,"");
3942 if (request.status != 200) {
3943 showDialog(getString("dialog_invalid_share_path"));
3944 return 0;
3945 }
3946 return 1;
3947}
3948function deleteShare(share) {
3949 shareToDelete = encodeURIComponent(share);
3950 var request = loadXMLDoc("/delete_share?share="+shareToDelete+"&tmp=","");
3951 if (request.status != 200) {
3952 showDialog(getString("dialog_failed_delete_share"));
3953 } else {
3954 window.location.reload();
3955 highlightNav($("#nav_synchronization"));
3956 }
3957}
3958
3959
3960function formatString(string2Fmt, values){
3961 if (string2Fmt == null || values == null || values.length == 0)
3962 return;
3963 var len = values.length;
3964 for (i=0;i<=len;i++){
3965 string2Fmt = string2Fmt.replace("{" + i + "}", values[i]);
3966 }
3967 return string2Fmt;
3968}
3969
3970function getWebDavUrl() {
3971 return "/rpc/webdavproxy?/rpc/webdav";
3972}
3973
3974// Helper function that does the actual RPC invocation
3975function loadXMLDoc(my_url,strData)
3976{
3977 var req;
3978 req = false;
3979 // branch for native XMLHttpRequest object
3980 if(window.XMLHttpRequest) {
3981 try {
3982 req = new XMLHttpRequest();
3983 } catch(e) {
3984 req = false;
3985 }
3986 // branch for IE/Windows ActiveX version
3987 } else if(window.ActiveXObject) {
3988 try {
3989 req = new ActiveXObject("Msxml2.XMLHTTP");
3990 } catch(e) {
3991 try {
3992 req = new ActiveXObject("Microsoft.XMLHTTP");
3993 } catch(e) {
3994 req = false;
3995 }
3996 }
3997 }
3998 if(req) {
3999 my_url = getWebDavUrl() + my_url;
4000 if (strData.length>0) { // post request
4001 req.open("POST", my_url, false);
4002 //req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
4003 req.setRequestHeader('Content-Type', 'text/xml; charset=UTF-8');
4004 req.send(strData);
4005 }
4006 else { // get request
4007 req.open("GET", my_url, false);
4008 try {
4009 req.send("");
4010 } catch(e) {
4011 req = false;
4012 }
4013 }
4014 return req;
4015 }
4016 return "";
4017}