View Javadoc
1   /******************************************************************************
2    * ContactManager.java - Manage making the contact sheets.  Insure single threadedness
3    * (two copies could lead to _heavens_ out of memory
4    * 
5    * PicMan - The BuckoSoft Picture Manager in Java
6    * Copyright(c) 2005 - Dick Balaska
7    * 
8    */
9   package com.buckosoft.PicMan.business;
10  
11  import java.io.File;
12  import java.util.ArrayList;
13  import java.util.Date;
14  import java.util.HashMap;
15  import java.util.Iterator;
16  import java.util.LinkedList;
17  import java.util.List;
18  import java.util.concurrent.locks.ReentrantLock;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import com.buckosoft.PicMan.business.contact.ContactEngine;
24  import com.buckosoft.PicMan.domain.Chain;
25  import com.buckosoft.PicMan.domain.ContactParams;
26  import com.buckosoft.PicMan.domain.Filter;
27  import com.buckosoft.PicMan.domain.FilterMicroSet;
28  import com.buckosoft.PicMan.domain.JobLogEntry;
29  import com.buckosoft.PicMan.domain.MetaSet;
30  import com.buckosoft.PicMan.domain.MetaSetRule;
31  import com.buckosoft.PicMan.domain.Pic;
32  import com.buckosoft.PicMan.domain.PosterParams;
33  import com.buckosoft.PicMan.domain.Set;
34  import com.buckosoft.PicMan.domain.SetSize;
35  import com.buckosoft.PicMan.domain.System;
36  
37  /**
38   * Contact Manager.  Decide when and what contact sheets to make.
39   * Singleton ContactManager gets invoked  
40   * @author Dick Balaska
41   * @since 2005/08/06
42   * @see <a href="http://cvs.buckosoft.com/Projects/PicMan/PicMan/src/main/java/com/buckosoft/PicMan/business/ContactManager.java">ContactManager.java</a>
43   */
44  public class ContactManager implements SetChangedListener {
45  
46  	private static boolean DEBUG = false;
47  	private static final boolean DEBUGENTRY = false;
48  	private static final boolean DEBUGSPIN = false;
49  
50  	protected final Log logger = LogFactory.getLog(getClass());
51  	
52  	// Debugging tools (manipulate the run)
53  //	private static final boolean runNone	= false;
54  //	private static final boolean runOnce	= false;
55  	private static final boolean onePicOnly	= false;
56  
57  	private	PicManFacade	pmf;
58  
59  	private	LinkedList<ContactParams>		contactList = new LinkedList<ContactParams>();
60  	private	LinkedList<ContactParams>		spinList = new LinkedList<ContactParams>();
61  	private	String			contactRunning;
62  	private	boolean			abortRequest = false;
63  	
64  	private	List<String>	cachedPicNames;
65  	private	ContactParams	cachedContactParams;
66  
67  	private	ReentrantLock cpLock = new ReentrantLock();
68  	private	ContactParams	currentContactParams;
69  	
70  	public	void setPicMan(PicManFacade pmf) {
71  		this.pmf = pmf;
72  		pmf.addSetChangedListener(this);
73  	}
74  
75  	/**
76  	 * @return the contactRunning
77  	 */
78  	public String getContactRunning() {
79  		return contactRunning;
80  	}
81  
82  	public	boolean	makeContacts() throws Exception {
83  		try {
84  			contactList = determineContactsToMake();
85  		} catch (Exception e) {
86  			// exitRun();
87  			throw e;
88  		}
89  		boolean workDone = false;
90  		if (DEBUGENTRY)
91  			logger.info("process contacts: " + contactList.size());
92  		Iterator<ContactParams> it = contactList.iterator();
93  		if (it.hasNext()) {
94  //			contactMaker = new ContactMaker();
95  //			contactMaker.setPicMan(pmf);
96  //			contactMaker.setContactManager(this);
97  			workDone = true;
98  		}
99  		try {
100 			JobLogEntry jle = null;
101 			while (it.hasNext()) {
102 				ContactParams cp = (ContactParams)it.next();
103 				setContactParamsRunning(cp);
104 				contactRunning = cp.getUuid();
105 				
106 				if (!pmf.getDB().getSystem().isJobLogSummaryOnly() || cp.getContactNumber() == 0) {
107 					if (DEBUG)
108 						logger.debug("Start log entry for " + cp.getContactNumber());
109 					jle = new JobLogEntry();
110 					//jle.setChainId(cp.getCid());
111 					jle.setNote(pmf.getDB().getChain(cp.getCid()).getName());
112 					if (!pmf.getDB().getSystem().isJobLogSummaryOnly())
113 						jle.setName(cp.getUuid());
114 					else
115 						jle.setName(cp.getUid());
116 					jle.setType(JobLogEntry.CONTACT);
117 					pmf.addJobToLog(jle);
118 				}
119 				ContactEngine	contactEngine = null;
120 				String	engineName = cp.getEngineName();
121 				if (cp instanceof PosterParams) {
122 					try {
123 						contactEngine = (ContactEngine)Class.forName("com.buckosoft.PicMan.business.contact.PosterEngine").newInstance();
124 					} catch (Exception e) {
125 						Exception ex = new Exception("Can't instantiate Poster Engine: ", e);
126 						pmf.addError(ex);
127 					}
128 					
129 				}
130 				try {
131 					if (contactEngine == null)
132 						contactEngine = (ContactEngine)Class.forName("com.buckosoft.PicMan.business.contact.engine." + engineName).newInstance();
133 				} catch (Exception e) {
134 					Exception ex = new Exception("Can't instantiate Contact Engine: " + engineName, e);
135 					pmf.addError(ex);
136 				}
137 				boolean ret = false;
138 				if (contactEngine != null) {
139 					contactEngine.setPicMan(pmf);
140 					contactEngine.setContactManager(this);
141 					ret = contactEngine.makeContact(cp);
142 				}
143 				if (isAbort())
144 					jle.setNote("*" + jle.getNote());
145 				if (ret == false) {
146 					jle.setError();
147 					jle.setEndTime();
148 					pmf.addJobToLog(jle);
149 				}
150 				if (!pmf.getDB().getSystem().isJobLogSummaryOnly() 
151 						|| cp.getContactNumber() >= pmf.getDB().getChain(cp.getCid()).getContactCount()-1
152 						|| cp instanceof PosterParams
153 						|| isAbort()) {	// turned off by user
154 					if (DEBUG)
155 						logger.debug("Finish log entry for " + cp.getContactNumber());
156 					jle.setEndTime();
157 					pmf.addJobToLog(jle);
158 				}
159 				if (isAbort()) {
160 					clearAbort();
161 					setContactParamsRunning(null);
162 					break;
163 				}
164 				removeFromSpin(cp);
165 			}
166 		} catch (Exception e) {
167 			//exitRun();
168 			throw e;
169 		}
170 		return(workDone);
171 	}
172 
173 	
174 	private	void setContactParamsRunning(ContactParams cp) {
175 		cpLock.lock();
176 		try {
177 			currentContactParams = cp;
178 		} finally {
179 			cpLock.unlock();
180 		}
181 	}
182 
183 	public ContactParams getContactParamsRunning() {
184 		ContactParams cp;
185 		cpLock.lock();
186 		try {
187 			cp = currentContactParams;
188 		} finally {
189 			cpLock.unlock();
190 		}
191 		return(cp);
192 	}
193 	/** Should we abort the current operation?
194 	 * First check if the engines were turned off.
195 	 * Then check if there is an abortRequest.  If there is, clear it and return true.
196 	 * @return Are we aborting?
197 	 */
198 	public boolean isAbort() {
199 		if (!pmf.getDB().getSystem().isEngineOn())
200 			return(true);
201 		if (!this.abortRequest)
202 			return(false);
203 		return(true);
204 	}
205 
206 	private void clearAbort() {
207 		this.abortRequest = false;
208 	}
209 
210 	/** If we happen to be building contacts for the set that changed, then kill it, because it
211 	 * has to be started over anyway.
212 	 * @see com.buckosoft.PicMan.business.SetChangedListener#onSetChanged(com.buckosoft.PicMan.domain.SetSize)
213 	 */
214 	@Override
215 	public void onSetChanged(SetSize ss) {
216 		cpLock.lock();
217 		try {
218 			if (currentContactParams != null 
219 					&& ss.getSize() == currentContactParams.getSize() 
220 					&& ss.getSetName().equals(currentContactParams.getSetName())) {
221 				this.abortRequest = true;
222 				logger.info("onSetChanged: aborting build of " + ss.getSetSize());
223 			}
224 		} finally {
225 			cpLock.unlock();
226 		}
227 	}
228 	
229 	
230 	/** Build a list of Contacts that need to be run
231 	 * 
232 	 * @return the list
233 	 */
234 	public 	LinkedList<ContactParams>	determineContactsToMake() {
235 		LinkedList<ContactParams> theList = new LinkedList<ContactParams>();
236 		theList.clear();
237 		List<Chain>	chains = pmf.getDB().getChains();
238 //		List	sets = pmf.getSets();
239 //		List	sizes = pmf.getSizes();
240 //		Iterator it = sets.iterator();
241 		HashMap<String, Date>		filterAgeMap = pmf.getDB().getSetTimestamps();
242 		Iterator<Chain>	iter = chains.iterator();
243 		while (iter.hasNext()) {
244 			Chain chain = (Chain)iter.next();
245 			if (!chain.isActive())
246 				continue;
247 			int		contactsPerSet = chain.getContactCount();
248 			HashMap<String, Date>		contactAgeMap = pmf.getDB().getContactOldestMap(chain);
249 //			Set set = (Set)it.next();
250 //			String	cSet = set.getName();
251 //			Iterator is = sizes.iterator();
252 			Iterator<SetSize> iss = chain.getSetSizes().iterator();
253 			while (iss.hasNext()) {
254 				SetSize ss = (SetSize)iss.next();
255 				//int size = ((Integer)is.next()).intValue();
256 
257 				if (!isSpin(chain.getCid(), ss.getSetName(), ss.getSize(), -1)) {	// isSpin doesn't do the tests and goes straight to build
258 					Date fdate = (Date)filterAgeMap.get(ss.getSetSize());
259 					Date cdate = (Date)contactAgeMap.get(ss.getGuiSetSize());
260 					if (DEBUG)
261 						logger.debug("Comparing: " + System.getUuid(ss.getSetName(), ss.getSize()) + " f:" + fdate + " c: " + cdate);
262 					Date fdateWild = (Date)filterAgeMap.get(ss.getSetName());
263 					if (fdateWild != null) {
264 						if (cdate != null) {
265 							if (fdateWild.after(cdate))	 {	// we have a wild card match.
266 								fdate = null;			// null out fdate so we fall through and build
267 								cdate = null;
268 							}
269 						}
270 					}
271 					if (fdate != null) {
272 						if (cdate != null) {
273 							if (DEBUG)
274 								logger.debug("class: f=" + fdate.getClass() + " c=" + cdate.getClass());
275 							if (fdate.before(cdate))
276 								continue;
277 						}
278 					} else {			// no filter date, assume old
279 						if (cdate != null)	// if there's a contact 
280 							continue;	// then don't rebuild it
281 					}
282 					if (DEBUG)
283 						logger.debug("Building set:  "  + ss.getSetSize());
284 				}
285 				for (int i=0; i<contactsPerSet; i++) {
286 					ContactParams cp = new ContactParams();
287 					cp.setContactNumber(i);
288 					cp.setCid(chain.getCid());
289 					cp.setSetSize(ss);
290 					cp.setRankMinMax(chain.getRatingMin(), chain.getRatingMax());
291 					cp.setEngineName(chain.getEngine());
292 					if (onePicOnly) {
293 						cp.setSize(300);
294 						cp.setSetName("X");
295 					}
296 					File dir = new File(chain.getContactDirectory() + File.separator + ss.getSetName());
297 					//logger.debug("try dir '" + dir.getPath() + "'");
298 					try {
299 						if (!dir.isDirectory())
300 							dir = dir.getParentFile();
301 					} catch (Exception e) {
302 						dir = dir.getParentFile();
303 					}
304 					//logger.debug("got dir '" + dir.getPath() + "'");
305 					cp.setOutputDirectory(dir.getPath());
306 					theList.add(cp);
307 					if (onePicOnly)
308 						return(theList);
309 				}
310 			}
311 		}
312 		for (ContactParams cp : spinList) {
313 			if (cp instanceof PosterParams)
314 				theList.add(cp);
315 		}
316 		return(theList);
317 	}
318 	
319 	public List<String> getPicNames(ContactParams cp) {
320 		if (cachedContactParams != null) {
321 			if (cp.equalsPics(cachedContactParams)) {
322 				ArrayList<String>	al = new ArrayList<String>(cachedPicNames);
323 				return(al);
324 			}
325 		}
326 		cachedPicNames = pmf.getDB().getPicNamesBySet(cp.getSetName(), cp.getSize());
327 //		cachedPicNames = setupPicsList(cp, cachedPicNames);			// XXX: Is this neccessary? It slowly prunes an already pruned list.
328 		cachedContactParams = cp;
329 		ArrayList<String>	al = new ArrayList<String>(cachedPicNames);
330 		return(al);
331 	}
332 	
333 	protected	List<String>	setupPicsList(ContactParams cp, List<String> picNames) {
334 //		if (cp.getType() == ContactParams.TYPE_SEQUENTIAL)
335 //			return(f);
336 		ArrayList<String>	al = new ArrayList<String>(picNames);
337 		Iterator<String> iter = al.iterator();
338 		while (iter.hasNext()) {
339 			String picName = iter.next();
340 			if (isFilteredOut(cp, picName))
341 				iter.remove();
342 		}
343 		return(al);
344 	}
345 	protected boolean isFilteredOut(ContactParams cp, String picName) {
346 		Filter f = pmf.getDB().getFilter(picName);
347 		double rating = 9;
348 		Set set = pmf.getDB().getSet(cp.getSetName());
349 		if (set.isMetaSet()) {
350 			// XXX: THIS NEEDS FIXING
351 			// XXX: THIS NEEDS FIXING
352 			// XXX: THIS NEEDS FIXING
353 			/* XXX: Still needs fixing.  There are actually two types (sigh) of MetaSet. MD is M+D/2. mlbXall is G|M|PG|R|XO|XX.  Any 7 should pass the large test.
354 			 * Ah.  The difference is as stated. M&D is different than G|M|PG|R|XO|XX
355 			 */
356 			rating = pmf.getDB().getPicRate(picName, set, cp.getSize());
357 			boolean nono = false;
358 			if (nono) {
359 				MetaSet ms = pmf.getDB().getMetaSet(set.getSid());
360 				List<MetaSetRule> msrList = ms.getRules();
361 				Iterator<MetaSetRule> msrIter = msrList.iterator();
362 				int	count = 0;
363 				while (msrIter.hasNext()) {
364 					//MetaSetRule msr = msrIter.next();
365 					//if (msr.)
366 					rating *= count;
367 					int prate = f.getFilter(cp.getSetName(), cp.getSize());
368 					rating += prate;
369 					count++;
370 					rating /= count;
371 				}
372 			}
373 		} else if (set.isMicroSet() || set.isNanoSet()) {
374 			Pic pic = pmf.getDB().getPic(picName);
375 			FilterMicroSet fms = pmf.getDB().getPicFromFilterMicroSet(pic.getPid(), set.getSid());
376 			if (fms != null)
377 				rating = fms.getValue();
378 			else
379 				rating = 0;
380 		} else {
381 			rating = f.getFilter(cp.getSetName(), cp.getSize());
382 		}
383 		logger.debug("isFilteredOut: " + picName + " " + rating);
384 		if (rating < cp.getRankMin()) 
385 			return(true);
386 		if (cp.getRankMax() == 9)	// passes the min test, no max. (hack to allow obsolete 10s from the database)
387 			return(false); 
388 		if (rating > cp.getRankMax())
389 			return(true);
390 		return(false);
391 	}
392 
393 	
394 	/** Add a list to the contacts to spin (remake)
395 	 * @param list A List of ContactParams to remake
396 	 */
397 
398 	public	void	addSpin(List<ContactParams> list) {
399 		Iterator<ContactParams> iter = list.iterator();
400 		while (iter.hasNext()) {
401 			ContactParams cp = iter.next();
402 			if (DEBUGSPIN)
403 				logger.info("addSpin: cp cid=" + cp.getCid() + " ss= " + cp.getSetName() + "_" + cp.getSize() + " cn=" + cp.getContactNumber());
404 			if (!isOnList(spinList, cp)) {
405 				spinList.add(cp);
406 			}
407 		}
408 	}
409 
410 	private	boolean isOnList(List<ContactParams> list, ContactParams cp) {
411 		Iterator<ContactParams> iter = list.iterator();
412 		while (iter.hasNext()) {
413 			ContactParams cpl = iter.next();
414 			if (cpl.equals(cp))
415 				return(true);
416 		}
417 		return(false);
418 	}
419 	/** Is this contact specififed on the spin list?
420 	 * @param cid The chainId
421 	 * @param setName The setName
422 	 * @param size The size
423 	 * @param contactNumber The contact sequence number
424 	 * @return true if this is a user specified spin request (on the spin list)
425 	 */
426 	private	boolean	isSpin(int cid, String setName, int size, int contactNumber) {
427 		Iterator<ContactParams> iter = spinList.iterator();
428 		ContactParams cp = new ContactParams(cid, setName, size, contactNumber);
429 		while (iter.hasNext()) {
430 			ContactParams cpl = iter.next();
431 			if (cpl.equals(cp))
432 				return(true);
433 		}
434 		return(false);
435 	}
436 	private	void	removeFromSpin(ContactParams cp) {
437 		if (DEBUGSPIN)
438 			logger.info("removeFromSpin: cp cid=" + cp.getCid() + " ss= " + cp.getSetName() + "_" + cp.getSize() + " cn=" + cp.getContactNumber());
439 		Iterator<ContactParams> iter = spinList.iterator();
440 		while (iter.hasNext()) {
441 			ContactParams cpl = iter.next();
442 			if (cpl.equals(cp)) {
443 				spinList.remove(cpl);
444 				return;
445 			}	
446 		}
447 	}
448 }