|
David@0
|
1 |
/* |
|
David@0
|
2 |
* Adium is the legal property of its developers, whose names are listed in the copyright file included |
|
David@0
|
3 |
* with this source distribution. |
|
David@0
|
4 |
* |
|
David@0
|
5 |
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU |
|
David@0
|
6 |
* General Public License as published by the Free Software Foundation; either version 2 of the License, |
|
David@0
|
7 |
* or (at your option) any later version. |
|
David@0
|
8 |
* |
|
David@0
|
9 |
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
|
David@0
|
10 |
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
David@0
|
11 |
* Public License for more details. |
|
David@0
|
12 |
* |
|
David@0
|
13 |
* You should have received a copy of the GNU General Public License along with this program; if not, |
|
David@0
|
14 |
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
David@0
|
15 |
*/ |
|
David@0
|
16 |
|
|
David@0
|
17 |
#import <Adium/AIContentControllerProtocol.h> |
|
David@0
|
18 |
#import "DCMessageContextDisplayPlugin.h" |
|
David@0
|
19 |
#import "DCMessageContextDisplayPreferences.h" |
|
David@0
|
20 |
#import <AIUtilities/AIDictionaryAdditions.h> |
|
David@0
|
21 |
#import <Adium/AIChat.h> |
|
David@0
|
22 |
#import <Adium/AIContentContext.h> |
|
David@715
|
23 |
#import <Adium/AIService.h> |
|
David@715
|
24 |
|
|
David@0
|
25 |
//Old school |
|
David@0
|
26 |
#import <Adium/AIListContact.h> |
|
David@0
|
27 |
#import <AIUtilities/AIAttributedStringAdditions.h> |
|
David@0
|
28 |
#import <Adium/AIAccountControllerProtocol.h> |
|
David@0
|
29 |
|
|
David@0
|
30 |
//omg crawsslinkz |
|
David@0
|
31 |
#import "AILoggerPlugin.h" |
|
David@0
|
32 |
|
|
David@0
|
33 |
//LMX |
|
David@0
|
34 |
#import <LMX/LMXParser.h> |
|
zacw@1226
|
35 |
#import <Adium/AIXMLElement.h> |
|
David@0
|
36 |
#import <AIUtilities/AIStringAdditions.h> |
|
David@0
|
37 |
#import "unistd.h" |
|
David@0
|
38 |
#import <AIUtilities/NSCalendarDate+ISO8601Parsing.h> |
|
David@0
|
39 |
#import <Adium/AIContactControllerProtocol.h> |
|
David@0
|
40 |
#import <Adium/AIHTMLDecoder.h> |
|
David@0
|
41 |
|
|
David@0
|
42 |
#define RESTORED_CHAT_CONTEXT_LINE_NUMBER 50 |
|
David@0
|
43 |
|
|
David@0
|
44 |
/** |
|
David@0
|
45 |
* @class DCMessageContextDisplayPlugin |
|
David@0
|
46 |
* @brief Component to display in-window message history |
|
David@0
|
47 |
* |
|
David@0
|
48 |
* The amount of history, and criteria of when to display history, are determined in the Advanced->Message History preferences. |
|
David@0
|
49 |
*/ |
|
David@84
|
50 |
@interface DCMessageContextDisplayPlugin () |
|
David@0
|
51 |
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key |
|
David@0
|
52 |
object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime; |
|
David@0
|
53 |
- (NSArray *)contextForChat:(AIChat *)chat; |
|
David@0
|
54 |
@end |
|
David@0
|
55 |
|
|
David@0
|
56 |
@implementation DCMessageContextDisplayPlugin |
|
David@0
|
57 |
|
|
David@0
|
58 |
/** |
|
David@0
|
59 |
* @brief Install |
|
David@0
|
60 |
*/ |
|
David@0
|
61 |
- (void)installPlugin |
|
David@0
|
62 |
{ |
|
David@0
|
63 |
isObserving = NO; |
|
David@0
|
64 |
|
|
David@0
|
65 |
//Setup our preferences |
|
David@95
|
66 |
[adium.preferenceController registerDefaults:[NSDictionary dictionaryNamed:CONTEXT_DISPLAY_DEFAULTS |
|
David@0
|
67 |
forClass:[self class]] |
|
David@0
|
68 |
forGroup:PREF_GROUP_CONTEXT_DISPLAY]; |
|
David@0
|
69 |
|
|
David@0
|
70 |
//Observe preference changes for whether or not to display message history |
|
David@95
|
71 |
[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_CONTEXT_DISPLAY]; |
|
David@0
|
72 |
} |
|
David@0
|
73 |
|
|
David@0
|
74 |
/** |
|
David@0
|
75 |
* @brief Uninstall |
|
David@0
|
76 |
*/ |
|
David@0
|
77 |
- (void)uninstallPlugin |
|
David@0
|
78 |
{ |
|
David@95
|
79 |
[adium.preferenceController unregisterPreferenceObserver:self]; |
|
David@1109
|
80 |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
David@0
|
81 |
} |
|
David@0
|
82 |
|
|
David@0
|
83 |
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key |
|
David@0
|
84 |
object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime |
|
David@0
|
85 |
{ |
|
David@0
|
86 |
if (!object) { |
|
David@0
|
87 |
shouldDisplay = [[prefDict objectForKey:KEY_DISPLAY_CONTEXT] boolValue]; |
|
David@3
|
88 |
linesToDisplay = [[prefDict objectForKey:KEY_DISPLAY_LINES] integerValue]; |
|
David@0
|
89 |
|
|
David@0
|
90 |
if (shouldDisplay && linesToDisplay > 0 && !isObserving) { |
|
David@0
|
91 |
//Observe new message windows only if we aren't already observing them |
|
David@0
|
92 |
isObserving = YES; |
|
David@1109
|
93 |
[[NSNotificationCenter defaultCenter] addObserver:self |
|
David@0
|
94 |
selector:@selector(addContextDisplayToWindow:) |
|
David@0
|
95 |
name:Chat_DidOpen |
|
David@0
|
96 |
object:nil]; |
|
David@0
|
97 |
|
|
David@0
|
98 |
} else if (isObserving && (!shouldDisplay || linesToDisplay <= 0)) { |
|
David@0
|
99 |
//Remove observer |
|
David@0
|
100 |
isObserving = NO; |
|
David@1109
|
101 |
[[NSNotificationCenter defaultCenter] removeObserver:self name:Chat_DidOpen object:nil]; |
|
David@0
|
102 |
|
|
David@0
|
103 |
} |
|
David@0
|
104 |
} |
|
David@0
|
105 |
} |
|
David@0
|
106 |
|
|
David@0
|
107 |
/** |
|
David@0
|
108 |
* @brief Retrieve and display in-window message history |
|
David@0
|
109 |
* |
|
David@0
|
110 |
* Called in response to the Chat_DidOpen notification |
|
David@0
|
111 |
*/ |
|
David@0
|
112 |
- (void)addContextDisplayToWindow:(NSNotification *)notification |
|
David@0
|
113 |
{ |
|
David@0
|
114 |
AIChat *chat = (AIChat *)[notification object]; |
|
David@0
|
115 |
|
|
David@0
|
116 |
NSArray *context = [self contextForChat:chat]; |
|
David@0
|
117 |
|
|
David@0
|
118 |
if (context && [context count] > 0 && shouldDisplay) { |
|
zacw@2820
|
119 |
AIContentContext *contextMessage; |
|
zacw@2820
|
120 |
|
|
zacw@2820
|
121 |
for(contextMessage in context) { |
|
zacw@2820
|
122 |
/* Don't display immediately, so the message view can aggregate multiple message history items. |
|
zacw@2820
|
123 |
* As required, we post Content_ChatDidFinishAddingUntrackedContent when finished adding. */ |
|
zacw@2820
|
124 |
[contextMessage setDisplayContentImmediately:NO]; |
|
David@0
|
125 |
|
|
zacw@2820
|
126 |
[adium.contentController displayContentObject:contextMessage |
|
zacw@2820
|
127 |
usingContentFilters:YES |
|
zacw@2820
|
128 |
immediately:YES]; |
|
zacw@2820
|
129 |
} |
|
David@0
|
130 |
|
|
zacw@2820
|
131 |
//We finished adding untracked content |
|
zacw@2820
|
132 |
[[NSNotificationCenter defaultCenter] postNotificationName:Content_ChatDidFinishAddingUntrackedContent |
|
zacw@2820
|
133 |
object:chat]; |
|
David@0
|
134 |
} |
|
David@0
|
135 |
} |
|
David@0
|
136 |
/*! |
|
David@0
|
137 |
* @brief Retrieve the message history for a particular chat |
|
David@0
|
138 |
* |
|
David@0
|
139 |
* Asks AILoggerPlugin for the path to the right file, and then uses LMX to parse that file backwards. |
|
David@0
|
140 |
*/ |
|
David@0
|
141 |
- (NSArray *)contextForChat:(AIChat *)chat |
|
David@0
|
142 |
{ |
|
David@0
|
143 |
//If there's no log there, there's no message history. Bail out. |
|
David@0
|
144 |
NSArray *logPaths = [AILoggerPlugin sortedArrayOfLogFilesForChat:chat]; |
|
David@0
|
145 |
if(!logPaths) return nil; |
|
zacw@2820
|
146 |
|
|
zacw@2820
|
147 |
NSInteger linesLeftToFind = 0; |
|
David@0
|
148 |
|
|
David@0
|
149 |
AIHTMLDecoder *decoder = [AIHTMLDecoder decoder]; |
|
David@0
|
150 |
|
|
David@721
|
151 |
NSString *logObjectUID = chat.name; |
|
David@426
|
152 |
if (!logObjectUID) logObjectUID = chat.listObject.UID; |
|
David@0
|
153 |
logObjectUID = [logObjectUID safeFilenameString]; |
|
David@0
|
154 |
|
|
David@0
|
155 |
NSString *baseLogPath = [[AILoggerPlugin logBasePath] stringByAppendingPathComponent: |
|
David@426
|
156 |
[AILoggerPlugin relativePathForLogWithObject:logObjectUID onAccount:chat.account]]; |
|
David@0
|
157 |
|
|
David@393
|
158 |
if ([chat boolValueForProperty:@"Restored Chat"] && linesToDisplay < RESTORED_CHAT_CONTEXT_LINE_NUMBER) { |
|
zacw@2820
|
159 |
linesLeftToFind = MAX(linesLeftToFind, RESTORED_CHAT_CONTEXT_LINE_NUMBER); |
|
David@0
|
160 |
} else { |
|
David@0
|
161 |
linesLeftToFind = linesToDisplay; |
|
David@0
|
162 |
} |
|
David@0
|
163 |
|
|
David@0
|
164 |
//Initialize a place to store found messages |
|
David@0
|
165 |
NSMutableArray *outerFoundContentContexts = [NSMutableArray arrayWithCapacity:linesLeftToFind]; |
|
David@0
|
166 |
|
|
zacw@2822
|
167 |
// These set of file's autorelease pool. |
|
zacw@2822
|
168 |
NSAutoreleasePool *parsingAutoreleasePool = [[NSAutoreleasePool alloc] init]; |
|
zacw@2822
|
169 |
|
|
David@0
|
170 |
//Iterate over the elements of the log path array. |
|
David@0
|
171 |
NSEnumerator *pathsEnumerator = [logPaths objectEnumerator]; |
|
David@0
|
172 |
NSString *logPath = nil; |
|
David@0
|
173 |
while (linesLeftToFind > 0 && (logPath = [pathsEnumerator nextObject])) { |
|
David@0
|
174 |
//If it's not a .chatlog, ignore it. |
|
David@0
|
175 |
if (![logPath hasSuffix:@".chatlog"]) |
|
David@0
|
176 |
continue; |
|
David@0
|
177 |
|
|
David@0
|
178 |
//Stick the base path on to the beginning |
|
David@0
|
179 |
logPath = [baseLogPath stringByAppendingPathComponent:logPath]; |
|
David@0
|
180 |
|
|
David@0
|
181 |
//By default, the xmlFilePath is the chat log file/bundle... if we find that the chatlog is a bundle, we'll use the xml file inside. |
|
David@0
|
182 |
NSString *xmlFilePath = logPath; |
|
David@0
|
183 |
|
|
David@0
|
184 |
BOOL isDir; |
|
David@0
|
185 |
if ([[NSFileManager defaultManager] fileExistsAtPath:logPath isDirectory:&isDir]) { |
|
David@0
|
186 |
/* If we have a chatLog bundle, we want to get the text content for the xml file inside */ |
|
David@0
|
187 |
NSString *baseURL; |
|
David@0
|
188 |
if (isDir) { |
|
David@0
|
189 |
baseURL = logPath; |
|
David@0
|
190 |
xmlFilePath = [logPath stringByAppendingPathComponent: |
|
David@0
|
191 |
[[[logPath lastPathComponent] stringByDeletingPathExtension] stringByAppendingPathExtension:@"xml"]]; |
|
David@0
|
192 |
} else { |
|
David@0
|
193 |
baseURL = nil; |
|
David@0
|
194 |
} |
|
David@0
|
195 |
[decoder setBaseURL:baseURL]; |
|
David@0
|
196 |
} |
|
David@0
|
197 |
|
|
David@0
|
198 |
//Initialize the found messages array and element stack for us-as-delegate |
|
zacw@2822
|
199 |
NSMutableArray *foundMessages = [NSMutableArray arrayWithCapacity:linesLeftToFind]; |
|
zacw@2822
|
200 |
NSMutableArray *elementStack = [NSMutableArray array]; |
|
David@0
|
201 |
|
|
David@0
|
202 |
//Create the parser and set ourselves as the delegate |
|
David@0
|
203 |
LMXParser *parser = [LMXParser parser]; |
|
David@0
|
204 |
[parser setDelegate:self]; |
|
David@0
|
205 |
|
|
David@0
|
206 |
//Set up info needed by elementStarted to create content objects. |
|
David@0
|
207 |
NSMutableDictionary *contextInfo = nil; |
|
David@0
|
208 |
{ |
|
David@0
|
209 |
//Get the service name from the path name |
|
David@0
|
210 |
NSString *serviceName = [[[[[logPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] lastPathComponent] componentsSeparatedByString:@"."] objectAtIndex:0U]; |
|
David@0
|
211 |
|
|
David@426
|
212 |
AIListObject *account = chat.account; |
|
David@715
|
213 |
NSString *accountID = [NSString stringWithFormat:@"%@.%@", account.service.serviceID, account.UID]; |
|
David@0
|
214 |
|
|
David@0
|
215 |
contextInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
|
David@0
|
216 |
serviceName, @"Service name", |
|
David@0
|
217 |
account, @"Account", |
|
David@0
|
218 |
accountID, @"Account ID", |
|
David@0
|
219 |
chat, @"Chat", |
|
David@0
|
220 |
decoder, @"AIHTMLDecoder", |
|
zacw@2820
|
221 |
[NSValue valueWithPointer:&linesLeftToFind], @"LinesLeftToFindValue", |
|
zacw@2822
|
222 |
foundMessages, @"FoundMessages", |
|
zacw@2822
|
223 |
elementStack, @"ElementStack", |
|
David@0
|
224 |
nil]; |
|
David@0
|
225 |
[parser setContextInfo:(void *)contextInfo]; |
|
David@0
|
226 |
} |
|
David@0
|
227 |
|
|
David@0
|
228 |
//Open up the file we need to read from, and seek to the end (this is a *backwards* parser, after all :) |
|
David@0
|
229 |
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:xmlFilePath]; |
|
David@0
|
230 |
[file seekToEndOfFile]; |
|
David@0
|
231 |
|
|
David@0
|
232 |
//Set up some more doohickeys and then start the parse loop |
|
David@3
|
233 |
NSInteger readSize = 4 * getpagesize(); //Read 4 pages at a time. |
|
David@0
|
234 |
NSMutableData *chunk = [NSMutableData dataWithLength:readSize]; |
|
David@3
|
235 |
NSInteger fd = [file fileDescriptor]; |
|
David@0
|
236 |
char *buf = [chunk mutableBytes]; |
|
David@0
|
237 |
off_t offset = [file offsetInFile]; |
|
David@0
|
238 |
enum LMXParseResult result = LMXParsedIncomplete; |
|
David@0
|
239 |
|
|
David@0
|
240 |
do { |
|
David@0
|
241 |
//Calculate the new offset |
|
David@0
|
242 |
offset = (offset <= readSize) ? 0 : offset - readSize; |
|
David@0
|
243 |
|
|
David@0
|
244 |
//Seek to it and read greedily until we hit readSize or run out of file. |
|
David@3
|
245 |
NSInteger idx = 0; |
|
David@0
|
246 |
for (ssize_t amountRead = 0; idx < readSize; idx += amountRead) { |
|
David@0
|
247 |
amountRead = pread(fd, buf + idx, readSize, offset + idx); |
|
David@0
|
248 |
if (amountRead <= 0) break; |
|
David@0
|
249 |
} |
|
David@0
|
250 |
offset -= idx; |
|
David@0
|
251 |
|
|
David@0
|
252 |
//Parse |
|
David@0
|
253 |
result = [parser parseChunk:chunk]; |
|
David@0
|
254 |
|
|
David@0
|
255 |
//Continue to parse as long as we need more elements, we have data to read, and LMX doesn't think we're done. |
|
David@0
|
256 |
} while ([foundMessages count] < linesLeftToFind && offset > 0 && result != LMXParsedCompletely); |
|
David@0
|
257 |
|
|
zacw@2822
|
258 |
//Drain our autorelease pool. |
|
zacw@2822
|
259 |
[parsingAutoreleasePool drain]; |
|
David@0
|
260 |
|
|
David@0
|
261 |
//Be a good citizen and close the file |
|
David@0
|
262 |
[file closeFile]; |
|
David@0
|
263 |
|
|
David@0
|
264 |
//Add our locals to the outer array; we're probably looping again. |
|
David@0
|
265 |
[outerFoundContentContexts replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:foundMessages]; |
|
David@0
|
266 |
linesLeftToFind -= [outerFoundContentContexts count]; |
|
David@0
|
267 |
} |
|
zacw@2820
|
268 |
|
|
zacw@2822
|
269 |
[parsingAutoreleasePool release]; |
|
zacw@2822
|
270 |
|
|
zacw@2820
|
271 |
if (linesLeftToFind > 0) { |
|
zacw@2820
|
272 |
AILogWithSignature(@"Unable to find %d logs for %@", linesLeftToFind, chat); |
|
zacw@2820
|
273 |
} |
|
zacw@2820
|
274 |
|
|
David@0
|
275 |
return outerFoundContentContexts; |
|
David@0
|
276 |
} |
|
David@0
|
277 |
|
|
David@0
|
278 |
#pragma mark LMX delegate |
|
David@0
|
279 |
|
|
David@0
|
280 |
- (void)parser:(LMXParser *)parser elementEnded:(NSString *)elementName |
|
David@0
|
281 |
{ |
|
zacw@2822
|
282 |
NSMutableDictionary *contextInfo = [parser contextInfo]; |
|
zacw@2822
|
283 |
NSMutableArray *elementStack = [contextInfo objectForKey:@"ElementStack"]; |
|
zacw@2822
|
284 |
|
|
David@0
|
285 |
if ([elementName isEqualToString:@"message"]) { |
|
zacw@1226
|
286 |
[elementStack insertObject:[AIXMLElement elementWithName:elementName] atIndex:0U]; |
|
David@0
|
287 |
} |
|
David@0
|
288 |
else if ([elementStack count]) { |
|
zacw@1226
|
289 |
AIXMLElement *element = [AIXMLElement elementWithName:elementName]; |
|
zacw@2798
|
290 |
[(AIXMLElement *)[elementStack objectAtIndex:0U] insertObject:element atIndex:0U]; |
|
David@0
|
291 |
[elementStack insertObject:element atIndex:0U]; |
|
David@0
|
292 |
} |
|
David@0
|
293 |
} |
|
David@0
|
294 |
|
|
David@0
|
295 |
- (void)parser:(LMXParser *)parser foundCharacters:(NSString *)string |
|
David@0
|
296 |
{ |
|
zacw@2822
|
297 |
NSMutableDictionary *contextInfo = [parser contextInfo]; |
|
zacw@2822
|
298 |
NSMutableArray *elementStack = [contextInfo objectForKey:@"ElementStack"]; |
|
zacw@2822
|
299 |
|
|
David@0
|
300 |
if ([elementStack count]) |
|
zacw@2798
|
301 |
[(AIXMLElement *)[elementStack objectAtIndex:0U] insertObject:string atIndex:0U]; |
|
David@0
|
302 |
} |
|
David@0
|
303 |
|
|
David@0
|
304 |
- (void)parser:(LMXParser *)parser elementStarted:(NSString *)elementName attributes:(NSDictionary *)attributes |
|
David@0
|
305 |
{ |
|
zacw@2822
|
306 |
NSMutableDictionary *contextInfo = [parser contextInfo]; |
|
zacw@2822
|
307 |
NSMutableArray *elementStack = [contextInfo objectForKey:@"ElementStack"]; |
|
zacw@2822
|
308 |
|
|
David@0
|
309 |
if ([elementStack count]) { |
|
zacw@1226
|
310 |
AIXMLElement *element = [elementStack objectAtIndex:0U]; |
|
David@0
|
311 |
if (attributes) { |
|
zacw@1226
|
312 |
[element setAttributeNames:[attributes allKeys] values:[attributes allValues]]; |
|
David@0
|
313 |
} |
|
David@0
|
314 |
|
|
zacw@2822
|
315 |
NSMutableArray *foundMessages = [contextInfo objectForKey:@"FoundMessagesValue"]; |
|
zacw@2822
|
316 |
NSInteger *linesLeftToFind = [[contextInfo objectForKey:@"LinesLeftToFindValue"] pointerValue]; |
|
zacw@2822
|
317 |
|
|
David@0
|
318 |
if ([elementName isEqualToString:@"message"]) { |
|
David@0
|
319 |
//A message element has started! |
|
zacw@1226
|
320 |
//This means that we have all of this message now, and therefore can create a single content object from the AIXMLElement tree and then throw away that tree. |
|
zacw@1226
|
321 |
//This saves memory when a message element contains many elements (since each one is represented by an AIXMLElement sub-tree in the AIXMLElement tree, as opposed to a simple NSAttributeRun in the NSAttributedString of the content object). |
|
David@0
|
322 |
|
|
David@0
|
323 |
NSString *serviceName = [contextInfo objectForKey:@"Service name"]; |
|
David@0
|
324 |
AIListObject *account = [contextInfo objectForKey:@"Account"]; |
|
David@0
|
325 |
NSString *accountID = [contextInfo objectForKey:@"Account ID"]; |
|
David@0
|
326 |
AIChat *chat = [contextInfo objectForKey:@"Chat"]; |
|
zacw@2820
|
327 |
|
|
David@0
|
328 |
//Set up some doohickers. |
|
zacw@1226
|
329 |
NSDictionary *attributes = [element attributes]; |
|
zacw@1226
|
330 |
NSString *timeString = [attributes objectForKey:@"time"]; |
|
David@0
|
331 |
//Create the context object |
|
David@0
|
332 |
if (timeString) { |
|
David@0
|
333 |
NSCalendarDate *time = [NSCalendarDate calendarDateWithString:timeString]; |
|
David@0
|
334 |
|
|
zacw@1226
|
335 |
NSString *autoreplyAttribute = [attributes objectForKey:@"auto"]; |
|
zacw@1226
|
336 |
NSString *sender = [NSString stringWithFormat:@"%@.%@", serviceName, [attributes objectForKey:@"sender"]]; |
|
zacw@1226
|
337 |
BOOL sentByMe = ([sender isEqualToString:accountID]); |
|
David@0
|
338 |
|
|
David@0
|
339 |
/*don't fade the messages if they're within the last 5 minutes |
|
David@0
|
340 |
*since that will be resuming a conversation, not starting a new one. |
|
David@0
|
341 |
*Why the class trickery? Less code duplication, clearer what is actually different between the two cases. |
|
David@0
|
342 |
*/ |
|
David@0
|
343 |
Class messageClass = (-[time timeIntervalSinceNow] > 300.0) ? [AIContentContext class] : [AIContentMessage class]; |
|
zacw@890
|
344 |
|
|
zacw@890
|
345 |
AIListContact *listContact = nil; |
|
zacw@890
|
346 |
|
|
zacw@890
|
347 |
if (chat.isGroupChat) { |
|
zacw@1226
|
348 |
listContact = [chat.account contactWithUID:[attributes objectForKey:@"sender"]]; |
|
zacw@890
|
349 |
} else { |
|
zacw@890
|
350 |
listContact = chat.listObject; |
|
zacw@890
|
351 |
} |
|
zacw@890
|
352 |
|
|
David@0
|
353 |
AIContentMessage *message = [messageClass messageInChat:chat |
|
zacw@890
|
354 |
withSource:(sentByMe ? account : listContact) |
|
zacw@890
|
355 |
destination:(sentByMe ? (chat.isGroupChat ? nil : chat.listObject) : account) |
|
David@0
|
356 |
date:time |
|
zacw@1226
|
357 |
message:[[contextInfo objectForKey:@"AIHTMLDecoder"] decodeHTML:[element contentsAsXMLString]] |
|
David@0
|
358 |
autoreply:(autoreplyAttribute && [autoreplyAttribute caseInsensitiveCompare:@"true"] == NSOrderedSame)]; |
|
David@0
|
359 |
|
|
David@0
|
360 |
//Don't log this object |
|
David@0
|
361 |
[message setPostProcessContent:NO]; |
|
David@0
|
362 |
[message setTrackContent:NO]; |
|
David@0
|
363 |
|
|
David@0
|
364 |
//Add it to the array (in front, since we're working backwards, and we want the array in forward order) |
|
David@0
|
365 |
[foundMessages insertObject:message atIndex:0]; |
|
David@0
|
366 |
} else { |
|
David@0
|
367 |
NSLog(@"Null message context display time for %@",element); |
|
David@0
|
368 |
} |
|
David@0
|
369 |
} |
|
zacw@2820
|
370 |
|
|
David@0
|
371 |
[elementStack removeObjectAtIndex:0U]; |
|
zacw@2820
|
372 |
if ([foundMessages count] == *linesLeftToFind) { |
|
David@0
|
373 |
if ([elementStack count]) [elementStack removeAllObjects]; |
|
David@0
|
374 |
[parser abortParsing]; |
|
David@0
|
375 |
} |
|
David@0
|
376 |
} |
|
David@0
|
377 |
} |
|
David@0
|
378 |
|
|
David@0
|
379 |
@end |