1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates the data source collection.
 26  * @class
 27  * This class represents a data source collection.
 28  * 
 29  * @extends		ZmModel
 30  */
 31 ZmDataSourceCollection = function() {
 32     ZmModel.call(this, ZmEvent.S_DATA_SOURCE);
 33 	this._initialized = false;
 34 	this._itemMap = {};
 35     this._pop3Map = {};
 36 	this._imapMap = {};
 37 };
 38 ZmDataSourceCollection.prototype = new ZmModel;
 39 ZmDataSourceCollection.prototype.constructor = ZmDataSourceCollection;
 40 
 41 //
 42 // Public methods
 43 //
 44 
 45 ZmDataSourceCollection.prototype.toString =
 46 function() {
 47 	return "ZmDataSourceCollection";
 48 };
 49 
 50 ZmDataSourceCollection.prototype.getItems = function() {
 51 	return AjxUtil.values(this._itemMap);
 52 };
 53 
 54 ZmDataSourceCollection.prototype.getItemsFor = function(folderId) {
 55     var accounts = [];
 56     for (var id in this._itemMap) {
 57         var account = this._itemMap[id];
 58         if (account.folderId == folderId && account.enabled) {
 59             accounts.push(account);
 60         }
 61     }
 62     return accounts;
 63 };
 64 
 65 /**
 66  * Gets the POP accounts.
 67  * 
 68  * @return	{Array}	an array of {@link ZmPopAccount} objects
 69  */
 70 ZmDataSourceCollection.prototype.getPopAccounts = function() {
 71     return AjxUtil.values(this._pop3Map);
 72 };
 73 
 74 /**
 75  * Gets the IMAP accounts.
 76  * 
 77  * @return	{Array}	an array of {@link ZmImapAccount} objects
 78  */
 79 ZmDataSourceCollection.prototype.getImapAccounts = function() {
 80     return AjxUtil.values(this._imapMap);
 81 };
 82 
 83 /**
 84  * Gets the POP accounts.
 85  * 
 86  * @param	{String}	folderId		the folder id
 87  * @return	{Array}	an array of {@link ZmPopAccount} objects
 88  */
 89 ZmDataSourceCollection.prototype.getPopAccountsFor = function(folderId) {
 90     var accounts = [];
 91     for (var id in this._pop3Map) {
 92         var account = this._pop3Map[id];
 93         if (account.folderId == folderId && account.enabled) {
 94             accounts.push(account);
 95         }
 96     }
 97     return accounts;
 98 };
 99 
100 /**
101  * Gets the IMAP accounts.
102  * 
103  * @param	{String}	folderId		the folder id
104  * @return	{Array}	an array of {@link ZmImapAccount} objects
105  */
106 ZmDataSourceCollection.prototype.getImapAccountsFor = function(folderId) {
107     var accounts = [];
108     for (var id in this._imapMap) {
109         var account = this._imapMap[id];
110         if (account.folderId == folderId && account.enabled) {
111             accounts.push(account);
112         }
113     }
114     return accounts;
115 };
116 
117 ZmDataSourceCollection.prototype.importMailFor = function(folderId) {
118 	this.importMail(this.getItemsFor(folderId));
119 };
120 
121 ZmDataSourceCollection.prototype.importPopMailFor = function(folderId) {
122 	this.importMail(this.getPopAccountsFor(folderId));
123 };
124 
125 ZmDataSourceCollection.prototype.importImapMailFor = function(folderId) {
126 	this.importMail(this.getImapAccountsFor(folderId));
127 };
128 
129 ZmDataSourceCollection.prototype.importMail = function(accounts) {
130     if (accounts && accounts.length > 0) {
131         var sourceMap = {};
132         var soapDoc = AjxSoapDoc.create("ImportDataRequest", "urn:zimbraMail");
133         for (var i = 0; i < accounts.length; i++) {
134             var account = accounts[i];
135             sourceMap[account.id] = account;
136 
137             var dsrc = soapDoc.set(account.ELEMENT_NAME);
138             dsrc.setAttribute("id", account.id);
139         }
140 
141 	    // send import request
142         var params = {
143             soapDoc: soapDoc,
144             asyncMode: true,
145 	        noBusyOverlay: true,
146             callback: null,
147             errorCallback: null
148         };
149         appCtxt.getAppController().sendRequest(params);
150 
151 	    // kick off check status request because import request
152 	    // doesn't return for (potentially) a looong time
153 	    var delayMs = 2000;
154 	    var action = new AjxTimedAction(this, this.checkStatus, [sourceMap, delayMs]);
155 	    AjxTimedAction.scheduleAction(action, delayMs);
156     }
157 };
158 
159 ZmDataSourceCollection.prototype.getById = function(id) {
160 	return this._itemMap[id];
161 };
162 
163 /**
164  * Gets a list of data sources associated with the given folder ID.
165  *
166  * @param {String}	folderId		[String]	the folderId
167  * @param {constant}	type			the type of data source (see <code>ZmAccount.TYPE_</code> constants)
168  * @return	{Array}	an array of items
169  * 
170  * @see	ZmAccount
171  */
172 ZmDataSourceCollection.prototype.getByFolderId = function(folderId, type) {
173 	var list = [];
174 	for (var id in this._itemMap) {
175 		var item = this._itemMap[id];
176 		if (item.folderId == folderId) {
177 			if (!type || (type && type == item.type))
178 				list.push(item);
179 		}
180 	}
181 	return list;
182 };
183 
184 ZmDataSourceCollection.prototype.add = function(item) {
185 	this._itemMap[item.id] = item;
186 	if (item.type == ZmAccount.TYPE_POP) {
187 		this._pop3Map[item.id] = item;
188 	}
189 	else if (item.type == ZmAccount.TYPE_IMAP) {
190 		this._imapMap[item.id] = item;
191 	}
192 	appCtxt.getIdentityCollection().add(item.getIdentity());
193 	this._notify(ZmEvent.E_CREATE, {item:item});
194 };
195 
196 ZmDataSourceCollection.prototype.modify = function(item) {
197 	appCtxt.getIdentityCollection().notifyModify(item.getIdentity(), true);
198     this._notify(ZmEvent.E_MODIFY, {item:item});
199 };
200 
201 ZmDataSourceCollection.prototype.remove = function(item) {
202     delete this._itemMap[item.id];
203 	delete this._pop3Map[item.id];
204 	delete this._imapMap[item.id];
205 	appCtxt.getIdentityCollection().remove(item.getIdentity());
206     this._notify(ZmEvent.E_DELETE, {item:item});
207 };
208 
209 ZmDataSourceCollection.prototype.initialize = function(dataSources) {
210 	if (!dataSources || this._initialized) { return; }
211 
212 	var errors = [];
213 
214 	if (appCtxt.get(ZmSetting.POP_ACCOUNTS_ENABLED)) {
215 		var popAccounts = dataSources.pop3 || [];
216 		for (var i = 0; i < popAccounts.length; i++) {
217 			var object = popAccounts[i];
218 			var dataSource = new ZmPopAccount(object.id);
219 			dataSource.setFromJson(object);
220 			this.add(dataSource);
221 			if (!dataSource.isStatusOk()) {
222 				errors.push(dataSource);
223 			}
224 		}
225 	}
226 
227 	if (appCtxt.get(ZmSetting.IMAP_ACCOUNTS_ENABLED)) {
228 		var imapAccounts = dataSources.imap || [];
229 		for (var i = 0; i < imapAccounts.length; i++) {
230 			var object = imapAccounts[i];
231 			var dataSource = new ZmImapAccount(object.id);
232 			dataSource.setFromJson(object);
233 			this.add(dataSource);
234 			if (!dataSource.isStatusOk()) {
235 				errors.push(dataSource);
236 			}
237 		}
238 	}
239 
240 	this._initialized = true;
241 
242 	var count = errors.length;
243 	if (count > 0) {
244 		// build error message
245 		var array = [
246 			AjxMessageFormat.format(ZmMsg.dataSourceFailureDescription, [count])
247 		];
248 		for (var i = 0; i < count; i++) {
249 			var dataSource = errors[i];
250 			var timestamp = Number(dataSource.failingSince);
251 			var lastError = dataSource.lastError;
252 			if (isNaN(timestamp)) {
253 				var pattern = ZmMsg.dataSourceFailureItem_noDate;
254 				var params = [AjxStringUtil.htmlEncode(dataSource.getName()), AjxStringUtil.htmlEncode(lastError)];
255 			} else {
256 				var pattern = ZmMsg.dataSourceFailureItem;
257 				var params = [AjxStringUtil.htmlEncode(dataSource.getName()), new Date(timestamp * 1000), AjxStringUtil.htmlEncode(lastError)];
258 			}
259 			array.push(AjxMessageFormat.format(pattern, params));
260 		}
261 		array.push(ZmMsg.dataSourceFailureInstructions);
262 		var message = array.join("");
263 
264 		// show error message
265 		var shell = DwtShell.getShell(window);
266 		var dialog = new DwtMessageDialog({parent:shell,buttons:[DwtDialog.OK_BUTTON,DwtDialog.CANCEL_BUTTON]});
267 		dialog.setMessage(message, DwtMessageDialog.CRITICAL_STYLE, ZmMsg.dataSourceFailureTitle);
268 		dialog.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this.__handleErrorDialogOk, [dialog]));
269 		dialog.popup();
270 	}
271 };
272 
273 ZmDataSourceCollection.prototype.__handleErrorDialogOk = function(dialog) {
274 	dialog.popdown();
275 
276 	var callback = new AjxCallback(this, this.__gotoPrefSection, ["ACCOUNTS"]);
277 	appCtxt.getAppController().activateApp(ZmApp.PREFERENCES, true, callback);
278 };
279 
280 ZmDataSourceCollection.prototype.__gotoPrefSection = function(prefSectionId) {
281 	var controller = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController();
282 	controller.getPrefsView().selectSection(prefSectionId);
283 };
284 
285 /**
286  * Periocially check status of the import
287  * @param {Object} sourceMap map of accounts
288  * @param {int} delayMs delay time between checks
289  */
290 ZmDataSourceCollection.prototype.checkStatus =
291 function(sourceMap, delayMs) {
292 	// Slowly back off import status checks but no more than 15 secs.
293 	if (delayMs && delayMs < 15000) {
294 		delayMs += 2000;
295 	}
296 
297     var soapDoc = AjxSoapDoc.create("GetImportStatusRequest", "urn:zimbraMail");
298     var callback = new AjxCallback(this, this._checkStatusResponse, [sourceMap, delayMs]);
299     var params = {
300         soapDoc: soapDoc,
301         asyncMode: true,
302         callback: callback,
303         errorCallback: null
304     };
305 
306     var appController = appCtxt.getAppController();
307     var action = new AjxTimedAction(appController, appController.sendRequest, [params]);
308     AjxTimedAction.scheduleAction(action, delayMs || 2000);
309 };
310 
311 //
312 // Protected methods
313 //
314 
315 ZmDataSourceCollection.prototype._checkStatusResponse =
316 function(sourceMap, delayMs, result) {
317 	var dataSources = [];
318 
319 	// gather sources from the response
320 	var popSources = result._data.GetImportStatusResponse.pop3;
321 	if (popSources) {
322 		for (var i in popSources) {
323 			dataSources.push(popSources[i]);
324 		}
325 	}
326 	var imapSources = result._data.GetImportStatusResponse.imap;
327 	if (imapSources) {
328 		for (var i in imapSources) {
329 			dataSources.push(imapSources[i]);
330 		}
331 	}
332 	var genericSources = result._data.GetImportStatusResponse.dsrc;
333 	if (genericSources) {
334 		for (var i in genericSources) {
335 			dataSources.push(genericSources[i]);
336 		}
337 	}
338 
339 	// is there anything to do?
340 	if (dataSources.length == 0) return;
341 
342 	// report status
343 	for (var i = 0; i < dataSources.length; i++) {
344 		var dsrc = dataSources[i];
345 		// NOTE: Only report the ones we were asked to; forget others
346 		if (!dsrc.isRunning && sourceMap[dsrc.id]) {
347 			var source = sourceMap[dsrc.id];
348 			if (sourceMap[dsrc.id]) {
349 				delete sourceMap[dsrc.id];
350 				if (dsrc.success) {
351 					var message = AjxMessageFormat.format(ZmMsg.dataSourceLoadSuccess, AjxStringUtil.htmlEncode(source.name));
352 					appCtxt.setStatusMsg(message);
353 				}
354 				else {
355 					var message = AjxMessageFormat.format(ZmMsg.dataSourceLoadFailure, AjxStringUtil.htmlEncode(source.name));
356 					appCtxt.setStatusMsg(message, ZmStatusView.LEVEL_CRITICAL);
357 					var dialog = appCtxt.getErrorDialog();
358 					dialog.setMessage(message, dsrc.error, DwtMessageDialog.CRITICAL_STYLE);
359 					dialog.popup();
360 				}
361 			}
362 		}
363 	}
364 
365 	// continue checking status
366 	if (AjxUtil.keys(sourceMap).length > 0) {
367 		this.checkStatus(sourceMap, delayMs);
368 	}
369 };
370