LuserNET-0.4.2/ 40775 764 764 0 10021220141 11217 5ustar alexalexLuserNET-0.4.2/.cvsignore100664 764 764 104 7434544651 13304 0ustar alexalexLuserNET.app shared_obj ChangeLog group_list.txt news.gdt news.gpr LuserNET-0.4.2/Changes100664 764 764 20154 10021217654 12650 0ustar alexalexThis is (supposed to be) a list of user-visible changes between versions. -- changes in version 0.4.1 - 2002-04-07 23:50 * Added posting support! Currently quite basic, but useable. The Compose menu will let you write a new article or reply to an existing. There's a preferences 'tab' that lets you set the default name and address, and the source of the default signature. The compose windows lets you change write the article and change the subject and from-address, as well as the list of groups the article should be posted to. If you want to crosspost (note that this is often considered bad style), write the group names separated by commas and nothing else (no whitespace). When you click post the article will be sent, and if it was sent succesfully the window will close. If you want to save a copy of the article locally, you'll need to copy and paste the text out of the window manually for now. If an article could not be sent, the log window will probably have an explanation. Note that GNUstep's NSTextView is quite slow, so when responding to a long article it might take a long while for the compose window to pop up. Note that correct format=flowed handling requires a future version of Pantomime, so the default posting method should be 'quoted-printable' (unless you are _really_ sure that you know what you're doing). * New key equivalents, 'alt-s' to save the current message and 'alt-F' to switch font. * Basic support for decoding uuencoded parts of messages. Note that this probably requires a future Pantomime version. * There's optional support for MIME handling (ie. inline display of images, html, rtf, etc.) using an not-yet released MIME handling library. If you're brave and want to test it, contact me. * The French and Spanish translations are still not up-to-date (and it's still my fault). -- changes in version 0.4.0 - 2002-03-25 01:15 * Added a French translation by Stéphane Peron. Thanks! * Added more read-ahead options to the preferences panel. * Updated the documentation. * Cleanups and bug fixes. Note that the French and Spanish translations are not currently up-to-date (which is mostly my fault). -- changes in version 0.3.9.4 (yet another test release) - 2002-03-11 02:50 * Added an Edit menu with a Copy entry. -- changes in version 0.3.9.3 (yet another test release) - 2002-03-11 02:30 * Implemented a real preferences panel. Info->Preferences will bring it up. There are currently 'tabs' for sources (basically the old preferences panel), message viewing, and read-ahead. (Almost) all options can be changed there, so there's no longer any need to mess around with default keys. Hopefully the descriptions there will be enough to understand the options. (If not, tell me what isn't clear and I'll do something about it.) * Fixed bugs. -- changes in version 0.3.9.2 (yet another test release) - 2002-03-09 18:30 * Added a size limit for automatic downloading of messages. The defaults key is 'AutoDownloadSizeLimit', and the default is 64kb. Messages larger than this won't be downloaded automatically when you select them, but you can still select download in the menu or click on the link in the message if you want to download them. The size of the message will be displayed (if known). * Fixed bug. -- changes in version 0.3.9.1 (another test release) - 2002-03-09 15:00 * Intelligent scrolling. When you hit ' ' to scroll down, quoted sections, blank lines and signatures will be automatically skipped. (This is optional (just set the defaults key "IntelligentScroll" to NO), but I find it very useful.) It should provide context for the part it skips to, but if it isn't enough you can use 's' and 'x' to get more. * Message rendering has been improved a lot. Headers and informative text from LuserNET is written in dark blue. Red text among headers are clickable. Normal message text is usually black, thought quoted parts might be colored differently (the colors have also been adjusted to look better). Data in attachments can saved by clicking 'Save'. In 'multipart/alternative' messages, you can click on the part you want to view (so you can view the text/plain instead of text/html). text/* parts that couldn't be decoded can be viewed as ascii text can be viewed as ASCII text (the ASCII text might not match what the sender intended, but it'll often be close). * Intelligent read-ahead. When it is active (defaults key 'ReadAhead', defaults is YES), LuserNET will start background downloads of the next, previous, next unread, and next-unread-past-this-thread messages. This means that by the time you've finished reading, hitting 'n' or ' ' will bring the next message up instantly. The defaults key 'ReadAheadSizeLimit' has the maximum size of a message that will be downloaded as read-ahead. The defaults value is 20kb. * There's a menu entry for showing the raw source for a message. * New headers will be parsed incrementally, instead of waiting until all have been downloaded and parsing them in one go. * Removed console version code from the released version. Still available by request, if anyone's interested. * Bug fixes. Optimizations. Cleanups. The usual. -- changes in version 0.3.9 (test release) - 2002-03-04 02:50 * If a thread is really deep the thread depth gets display as a number. * Headers will be parsed from the message's data if we didn't get them with xover. * If a message doesn't have a source the 'no source'-message will let you select one. * Key navigation should work in most dialogs. * Updated for new Pantomime changes. (Won't work with old versions.) * Bug fixes. Meta header database is slightly incompatible at the moment. -- changes in version 0.3.4 - 2002-02-26 13:00 * 's' and 'x' move up and down a small amount in the current message. * Added a spanish translation contributed by Quique. Thanks! * Message->Download will download a message. Usually messages are downloaded automatically, but if there was an error you can use this to force a new download attempt (useful when messages have arrived out of order). * 'Message->Switch font' will switch between the primary and secondary font. The default keys MessageFont1 and MessageFont1Size specify the primary font, and MessageFont2/... the secondary font. By default the primary font is the normal user font and the secondary font is the normal user fixed-pitch font. No GUI to change this yet... * The defaults key ColorMessages controls whether messages are colored or not. Default is to color, you can change it by running: defaults write LuserNET ColorMessages NO (or YES). No GUI for this yet... * Removed yellow and green from the list of colors to use when coloring messages since they were difficult to read on the white background. * Fix some silly typos in the localization files. Start using make_strings to keep them up-to-date. * Fixed a bug that caused a 'FolderSortMode_(nil)' key to appear in the defaults (it causes no harm, but it doesn't do anything and shouldn't be there). * Disabled double-release checking. This increases performance a fair amount (especially when parsing and coloring large messages). -- changes in version 0.3.3 - 2002-02-23 23:50 * Messages can be saved from the menu. The raw data will be saved, including headers. * The date for messages is now parsed correctly and will be displayed intelligently in the folder window. * Messages can be arranged by thread, reverse-thread (newer threads above older threads), arrival order and reverse arrival order. Thread-based movement commands only work in normal thread mode currently. * The command line configuration stuff is gone. * The menus have been rearranged. * 'P' will move to a message's parent. * The program will remember which windows/folders you had open when you exited the program and will open them again when you start it. * Lots of minor fixed, cleanups and optimizations. -- changes in version 0.3.2 - 2002-02-19 23:07 * Fix bug in handling 'A' in folder windows (it'd work but it'd beep anyway). * Added localization support and a swedish translation. * Menus have been rearranged and extended. * Table column positions will be saved. -- version 0.3.1 - 2002-02-19 01:54 LuserNET-0.4.2/ComposeWindowController.h100664 764 764 1274 10021217655 16352 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef ComposeWindowController_h #define ComposeWindowController_h @class NSString,NSDictionary; @class Message; @class NSTextView,NSTextField,NSButton; @interface ComposeWindowController : NSWindowController { int state; NSDictionary *msg_headers; NSTextView *text; NSTextField *tf_from,*tf_newsgroups,*tf_subject; NSButton *b_send; } /* designated initializer */ - initWithHeaders: (NSDictionary *)headers content: (NSString *)content; - initWithHeaders: (NSDictionary *)headers quoteContent: (NSString *)content; - initWithHeaders: (NSDictionary *)headers; - initWithFollowupToMessage: (Message *)msg; @end #endif LuserNET-0.4.2/ComposeWindowController.m100664 764 764 24313 10021217655 16376 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ComposeWindowController.h" #include "main.h" #include "KeyWindow.h" #include "Pref_Posting.h" #include #include #include #include @implementation ComposeWindowController -(void) post: (id)sender { Message *m=[[Message alloc] init]; NSMutableDictionary *md; NSEnumerator *e; NSString *header,*value; NSData *d; if (state) { NSBeep(); return; } if (![[tf_subject stringValue] length] || ![[tf_newsgroups stringValue] length] || ![[tf_from stringValue] length]) { NSBeep(); return; } md=[msg_headers mutableCopy]; [md setObject: [tf_subject stringValue] forKey: @"Subject"]; [md setObject: [tf_newsgroups stringValue] forKey: @"Newsgroups"]; [md setObject: [tf_from stringValue] forKey: @"From"]; for (e=[md keyEnumerator];(header=[e nextObject]);) { value=[md objectForKey: header]; if ([header isEqualToString: @"From"]) { InternetAddress *ia=[[InternetAddress alloc] initWithString: value]; [m setFrom: ia]; DESTROY(ia); } else if ([header isEqualToString: @"Subject"]) { [m setSubject: value]; } else [m addHeader: header withValue: value]; } [m setContentType: @"text/plain"]; [m setContentTransferEncoding: PantomimeEncoding8bit]; [m setFormat: PantomimeFormatFlowed]; [m setContent: [text string]]; [m setCharset: [MimeUtility charsetForString: [text string]]]; state=1; d=[m dataValue]; { NSData *nd=[[[@"Newsgroups: " stringByAppendingString: [md objectForKey: @"Newsgroups"]] stringByAppendingString: @"\n"] dataUsingEncoding: NSASCIIStringEncoding]; nd=[nd mutableCopy]; [(NSMutableData *)nd appendData: d]; d=nd; AUTORELEASE(nd); } if ([md objectForKey: @"References"]) { NSData *nd=[[[@"References: " stringByAppendingString: [md objectForKey: @"References"]] stringByAppendingString: @"\n"] dataUsingEncoding: NSASCIIStringEncoding]; nd=[nd mutableCopy]; [(NSMutableData *)nd appendData: d]; d=nd; AUTORELEASE(nd); } /* TODO: this needs to be done in some better way */ [app_delegate postArticleFrom: self : [d bytes] : [d length]]; DESTROY(md); DESTROY(m); } -(void) postedArticle: (BOOL)success { if (!success) { state=0; NSBeep(); } else { state=2; [[self window] performClose: self]; } } -(void) windowWillClose: (NSNotification *)n { [self autorelease]; } -(void) dealloc { DESTROY(msg_headers); [super dealloc]; } - initWithHeaders: (NSDictionary *)headers content: (NSString *)content { NSWindow *win; win=[[KeyWindow alloc] initWithContentRect: NSMakeRect(100,100,550,500) styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask backing: NSBackingStoreRetained defer: YES]; if (!(self=[super initWithWindow: win])) return nil; msg_headers=[headers copy]; state=0; { NSString *signature=[Pref_Posting postingSignature]; if (signature) { content=[[content stringByAppendingString: @"\n-- \n"] stringByAppendingString: signature]; } } { GSVbox *vbox; vbox=[[GSVbox alloc] init]; [vbox setDefaultMinYMargin: 4]; [vbox setBorder: 2]; { NSScrollView *sv; NSTextView *tv; sv=[[NSScrollView alloc] initWithFrame: NSMakeRect(0,0,1,1)]; [sv setHasVerticalScroller: YES]; [sv setHasHorizontalScroller: YES]; [sv setBorderType: NSBezelBorder]; [sv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; text=tv=[[NSTextView alloc] initWithFrame: [[sv contentView] frame]]; [tv setHorizontallyResizable: NO]; [tv setVerticallyResizable: YES]; [tv setEditable: YES]; [[tv textContainer] setWidthTracksTextView: YES]; [[tv textContainer] setHeightTracksTextView: NO]; [[tv textContainer] setContainerSize: NSMakeSize(1e6,1e6)]; [tv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; if (content) [tv setString: content]; [sv setDocumentView: tv]; [vbox addView: sv enablingYResizing: YES]; [sv release]; [tv release]; } [vbox addSeparator]; { GSHbox *hbox; NSButton *b; NSTextField *f; hbox=[[GSHbox alloc] init]; [hbox setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [hbox setDefaultMinXMargin: 2]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Subject:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hbox addView: f enablingXResizing: NO]; DESTROY(f); tf_subject=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hbox addView: f enablingXResizing: YES]; DESTROY(f); [vbox addView: hbox enablingYResizing: NO]; DESTROY(hbox); hbox=[[GSHbox alloc] init]; [hbox setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [hbox setDefaultMinXMargin: 2]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Newsgroups (separate with commas):")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hbox addView: f enablingXResizing: NO]; DESTROY(f); tf_newsgroups=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hbox addView: f enablingXResizing: YES]; DESTROY(f); [vbox addView: hbox enablingYResizing: NO]; DESTROY(hbox); hbox=[[GSHbox alloc] init]; [hbox setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [hbox setDefaultMinXMargin: 2]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"From:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hbox addView: f enablingXResizing: NO]; DESTROY(f); tf_from=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hbox addView: f enablingXResizing: YES]; DESTROY(f); b_send=b=[[NSButton alloc] init]; [b setTitle: _(@"Post")]; [b sizeToFit]; [b setTarget: self]; [b setAction: @selector(post:)]; [hbox addView: b enablingXResizing: NO]; DESTROY(b); [vbox addView: hbox enablingYResizing: NO]; DESTROY(hbox); } [win setContentView: vbox]; [vbox release]; } if ([msg_headers objectForKey: @"Subject"]) [tf_subject setStringValue: [msg_headers objectForKey: @"Subject"]]; if ([msg_headers objectForKey: @"Newsgroups"]) [tf_newsgroups setStringValue: [msg_headers objectForKey: @"Newsgroups"]]; if ([msg_headers objectForKey: @"From"]) [tf_from setStringValue: [msg_headers objectForKey: @"From"]]; else { NSString *name,*from; name=[Pref_Posting postingName]; from=[Pref_Posting fromAddress]; if (from) { if (name) [tf_from setStringValue: [NSString stringWithFormat: @"\"%@\" <%@>",name,from]]; else [tf_from setStringValue: from]; } } [win setTitle: [tf_subject stringValue]]; [win setDelegate: self]; [win release]; return self; } /* returns a retained string */ -(NSMutableString *) _quoteString: (NSString *)content to: (NSMutableString *)astr { NSMutableString *str; NSString *line; NSArray *lines; int i,c; lines=[content componentsSeparatedByString: @"\n"]; c=[lines count]; if (astr) str=astr; else str=[[NSMutableString alloc] initWithCapacity: [content length]+2*c]; for (i=0;i0 && [line characterAtIndex: 0]=='>') [str appendString: @">"]; else [str appendString: @"> "]; [str appendString: line]; [str appendString: @"\n"]; } return str; } - initWithHeaders: (NSDictionary *)headers quoteContent: (NSString *)content { NSString *str; if (!content) return [self initWithHeaders: headers content: nil]; str=[self _quoteString: content to: nil]; self=[self initWithHeaders: headers content: str]; DESTROY(str); return self; } - initWithHeaders: (NSDictionary *)headers { return [self initWithHeaders: headers content: nil]; } /* TODO: handle attributation, followup to sender, followup, etc. */ - initWithFollowupToMessage: (Message *)msg { NSDictionary *headers; NSString *subject,*references; NSString *msgid; int i; references=[msg headerValueForName: @"References"]; msgid=[msg messageID]; if ([msgid characterAtIndex: 0]!='<') msgid=[[@"<" stringByAppendingString: [msg messageID] ] stringByAppendingString: @">"]; if (!references) references=msgid; else { references=[[references stringByAppendingString: @" "] stringByAppendingString: msgid]; } subject=[msg subject]; if ([subject length]>=3 && [subject characterAtIndex: 2]==':') { for (i=3;i<[subject length];i++) if ([subject characterAtIndex: i]>32) break; subject=[subject substringFromIndex: i]; } subject=[@"Re: " stringByAppendingString: subject]; headers=[NSDictionary dictionaryWithObjectsAndKeys: subject,@"Subject", references,@"References", [msg headerValueForName: @"Newsgroups"],@"Newsgroups", nil]; { NSMutableString *str; str=[[NSMutableString alloc] init]; if ([[msg from] personal] && [[[msg from] personal] length]) [str appendString: [NSString stringWithFormat: @"%@ wrote:\n",[[msg from] personal]]]; else [str appendString: [NSString stringWithFormat: @"%@ wrote:\n",[[msg from] address]]]; if ([[msg content] isKindOfClass: [NSString class]]) { NSString *content=(NSString *)[msg content]; str=[self _quoteString: content to: str]; } else [str appendString: @"> (currently) unquoteable content\n"]; return [self initWithHeaders: headers content: str]; DESTROY(str); } } @end LuserNET-0.4.2/FolderListController.h100664 764 764 662 10021217655 15604 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef FolderListController_h #define FolderListController_h #include @class MsgDB; @class NSMutableArray,NSTableView,NSTableColumn; @interface FolderListController : NSWindowController { MsgDB *mdb; NSMutableArray *folder_names; NSTableView *folders; NSTableColumn *c_name,*c_num; } - initWithMsgDB: (MsgDB *)m; @end #endif LuserNET-0.4.2/FolderListController.m100664 764 764 7306 10021217655 15633 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include "FolderListController.h" #include "MsgDB.h" #include "main.h" @implementation FolderListController -(void) updateFolderNames { NSEnumerator *e; NSString *s; DESTROY(folder_names); folder_names=[[NSMutableArray alloc] init]; for (e=[[mdb folders] keyEnumerator];(s=[e nextObject]);) [folder_names addObject: [s copy]]; [folder_names sortUsingSelector: @selector(compare:)]; [folders reloadData]; } -(int) numberOfRowsInTableView: (NSTableView *)tv { return [folder_names count]; } -(id) tableView: (NSTableView *)tv objectValueForTableColumn: (NSTableColumn *)tc row: (int)row { if (tc==c_name) return [folder_names objectAtIndex: row]; else if (tc==c_num) return [NSNumber numberWithInt: [[[mdb folders] objectForKey: [folder_names objectAtIndex: row]] numMessages]]; else abort(); } - initWithMsgDB: (MsgDB *)m; { NSWindow *win; win=[[NSWindow alloc] initWithContentRect: NSMakeRect(100,100,250,200) styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask backing: NSBackingStoreRetained defer: YES]; if (!(self=[super initWithWindow: win])) return nil; ASSIGN(mdb,m); folder_names=[[NSMutableArray alloc] init]; { NSScrollView *sv; [win setTitle: _(@"Folder list")]; c_name=[[NSTableColumn alloc] initWithIdentifier: @"Name"]; [[c_name headerCell] setStringValue: _(@"Name")]; [c_name setEditable: NO]; [c_name setResizable: YES]; [c_name setWidth: 180]; c_num=[[NSTableColumn alloc] initWithIdentifier: @"Messages"]; [[c_num headerCell] setStringValue: _(@"Messages")]; [[c_num headerCell] setAlignment: NSRightTextAlignment]; [[c_num dataCell] setAlignment: NSRightTextAlignment]; [c_num setEditable: NO]; [c_num setResizable: YES]; [c_num setWidth: 50]; folders=[[NSTableView alloc] init]; [folders setAllowsColumnReordering: YES]; [folders setAllowsColumnResizing: YES]; [folders setAllowsMultipleSelection: NO]; [folders setAllowsColumnSelection: NO]; [folders addTableColumn: c_name]; [folders addTableColumn: c_num]; [folders setDataSource: self]; [folders setDoubleAction: @selector(openFolder:)]; [folders setAutosaveName: @"FolderList"]; [folders setAutosaveTableColumns: YES]; sv=[[NSScrollView alloc] init]; [sv setDocumentView: folders]; [sv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [sv setAutoresizesSubviews: YES]; [sv setHasVerticalScroller: YES]; [sv setBorderType: NSBezelBorder]; [win setContentView: sv]; [sv release]; } [win setDelegate: self]; [win setFrameUsingName: @"FolderList"]; [win setFrameAutosaveName: @"FolderList"]; [win release]; [self updateFolderNames]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateFolderNames) name: MsgDB_FolderAddMsgNotification object: mdb]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateFolderNames) name: MsgDB_FolderAddNotification object: mdb]; return self; } -(void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver: self]; DESTROY(c_name); DESTROY(c_num); DESTROY(folders); DESTROY(folder_names); DESTROY(mdb); [super dealloc]; } -(void) display { [[self window] makeKeyAndOrderFront: self]; } -(void) openFolder: (id)sender { int r=[folders selectedRow]; if (r>=0 && r<[folder_names count]) [app_delegate openFolderWindow: [folder_names objectAtIndex: r]]; } @end LuserNET-0.4.2/FolderThreader.h100664 764 764 1763 7441432720 14372 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef FolderThreader_h #define FolderThreader_h #include "MsgDB.h" typedef struct { msg_id_t mid; char rstatus,level; unsigned char cached; } msg_info_t; @protocol FolderThreaderTarget -(void) selectMessage: (int)num; @end @interface FolderThreader : NSObject { @public msg_info_t *msgs; int num_msgs; MsgDB *mdb; id t; struct msg_header_cache_s *header_cache; } - initWithMsgDB: (MsgDB *)mdb target: (id)target; -(int) addMsg: (msg_id_t)mid; -(int) indexOf: (msg_id_t)mid; -(BOOL) selectFirstUnreadFrom: (int)i; -(int) findNextUnreadFrom: (int)i; -(int) findNextMax: (int)max from: (int)i; -(int) findPrevMax: (int)max from: (int)i; -(void) markAsRead: (int)from : (int)to; -(NSString *) header: (const char *)h forMessage: (int)msg; -(NSString *) subjectForMessage: (int)msg; -(NSString *) fromForMessage: (int)msg; -(NSDate *) dateForMessage: (int)msg; @end #endif LuserNET-0.4.2/FolderThreader.m100664 764 764 27070 10021217655 14432 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include "FolderThreader.h" #include static NSDate *parse_date(const char *date_header) { char *buf; char *b,*c; const char *d; NSDate *date; NSTimeZone *tz; int day,month,year,hour,minute,second,ofs; buf=malloc(strlen(date_header)+1); if (!buf) return [[NSDate alloc] initWithTimeIntervalSince1970: 0]; { int comma=0,paren=0; for (b=buf,d=date_header;*d;d++) { /* TODO: escaped '(' and ')'? */ if (*d=='(') paren++; else if (*d==')') { if (paren) paren--; } else if (paren) continue; else if (*d==',' && !comma) { comma=1; b=buf; } else *b++=*d; } } *b=0; b=buf; while (*b && !isdigit(*b)) b++; if (!*b) goto error; day=strtol(b,&b,10); while (*b && !isalpha(*b)) b++; if (!*b) goto error; for (c=b;isalpha(*c);c++) ; *c=0; if (!strcasecmp(b,"jan")) month=1; else if (!strcasecmp(b,"feb")) month=2; else if (!strcasecmp(b,"mar")) month=3; else if (!strcasecmp(b,"apr")) month=4; else if (!strcasecmp(b,"may")) month=5; else if (!strcasecmp(b,"jun")) month=6; else if (!strcasecmp(b,"jul")) month=7; else if (!strcasecmp(b,"aug")) month=8; else if (!strcasecmp(b,"sep")) month=9; else if (!strcasecmp(b,"oct")) month=10; else if (!strcasecmp(b,"nov")) month=11; else if (!strcasecmp(b,"dec")) month=12; else month=1; *c=' '; /* there had to be whitespace there before */ while (!isdigit(*b)) b++; if (!*b) goto error; year=strtol(b,&c,10); if (c-b==2) { if (year<50) year+=2000; else year+=1900; } else if (c-b==3) year+=1900; b=c; while (*b && !isdigit(*b)) b++; if (!*b) goto error; hour=strtol(b,&b,10); while (*b && !isdigit(*b)) b++; if (!*b) goto error; minute=strtol(b,&b,10); if (*b==':') { while (*b && !isdigit(*b)) b++; if (!*b) goto error; second=strtol(b,&b,10); } else second=0; while (isspace(*b)) b++; c=b; while (*c && !isspace(*c)) c++; *c=0; if ((b[0]=='+' || b[0]=='-')) { ofs=strtol(&b[1],&c,10); ofs=ofs%100+(ofs/100)*60; if (b[0]=='-') ofs=-ofs; } else ofs=0; /* TODO: handle others? */ if (month<1 || month>12 || day<1 || day>31 || hour<0 || hour>23 || minute<0 || minute>59 || second<0 || second>65) /* margin for leap seconds, probably too much */ goto error; free(buf); tz=[NSTimeZone timeZoneForSecondsFromGMT: ofs*60]; date=[[NSCalendarDate alloc] initWithYear: year month: month day: day hour: hour minute: minute second: second timeZone: tz]; return date; error: free(buf); /* let NSDate have a go at it, maybe it'll extract something useful */ return RETAIN([NSDate dateWithNaturalLanguageString: [NSString stringWithCString: c]]); } #define CACHE_SIZE 100 /* can't go higher than 255; entry 0 isn't used */ static int cache_hits,cache_misses; typedef struct msg_header_cache_s { NSString *subject,*from; NSDate *date; int row; unsigned short next,prev; /* Entries are in a linked list. Used entries are moved to the front, entries at the end are replaced as necessary. Entry #0 is the head of the list (and is included in the list). */ } header_cache_t; @implementation FolderThreader -(void) _sanity { int i,j; for (i=0;icache map!\n"); *((int *)-1)=0; } } for (i=0;imsg map!\n"); *((int *)-1)=0; } } for (i=header_cache[0].next,j=0;i;i=header_cache[i].next) j++; if (j!=CACHE_SIZE-1) { fprintf(stderr,"header cache messed up, linked list next!\n"); *((int *)-1)=0; } for (i=header_cache[0].prev,j=0;i;i=header_cache[i].prev) j++; if (j!=CACHE_SIZE-1) { fprintf(stderr,"header cache messed up, linked list prev!\n"); *((int *)-1)=0; } } -(void) _moveFirst: (int)e { if (!e) abort(); if (e==header_cache[0].next) return; /* remove from old place */ header_cache[header_cache[e].prev].next=header_cache[e].next; header_cache[header_cache[e].next].prev=header_cache[e].prev; /* update entry */ header_cache[e].next=header_cache[0].next; header_cache[e].prev=0; /* insert in new */ header_cache[header_cache[0].next].prev=e; header_cache[0].next=e; } -(void) _moveLast: (int)e { if (!e) abort(); if (e==header_cache[0].prev) return; /* remove from old place */ header_cache[header_cache[e].prev].next=header_cache[e].next; header_cache[header_cache[e].next].prev=header_cache[e].prev; /* update entry */ header_cache[e].prev=header_cache[0].prev; header_cache[e].next=0; /* insert in new */ header_cache[header_cache[0].prev].next=e; header_cache[0].prev=e; } -(void) _clearCacheEntry: (MidNotification *)n { header_cache_t *h; int r=[self indexOf: [n mid]]; if (r==-1) return; if (!msgs[r].cached) return; h=&header_cache[msgs[r].cached]; msgs[r].cached=0; DESTROY(h->subject); DESTROY(h->from); DESTROY(h->date); h->row=-1; [self _moveLast: h-header_cache]; } - initWithMsgDB: (MsgDB *)m target: (id)target { if (!(self=[super init])) return nil; ASSIGN(mdb,m); t=target; msgs=NULL; num_msgs=0; header_cache=malloc(sizeof(header_cache_t)*CACHE_SIZE); if (!header_cache) { RELEASE(self); return nil; } memset(header_cache,0,sizeof(header_cache_t)*CACHE_SIZE); { int i; for (i=0;isubject); DESTROY(h->from); DESTROY(h->date); } free(header_cache); header_cache=NULL; } msgs=NULL; num_msgs=0; [super dealloc]; // printf(" hits=%8i\nmisses=%8i\n",cache_hits,cache_misses); } -(int) indexOf: (msg_id_t)mid { /* TODO: this is probably a bottleneck */ int i; msg_info_t *m; for (i=0,m=msgs;imid==mid) return i; return -1; } /* TODO: think hard about how to handle cross posts and references to articles from other groups. */ -(int) addMsg: (msg_id_t)mid { /* Keep track of adds in progress so circular references can be detected. */ static msg_id_t *adding_stack; static int adding_stack_num,adding_stack_size; msg_info_t *m; char *c,*d; const char *mh; char *buf; int after; msg_id_t amid; { int i; for (i=0;i'); if (!d) break; /* broken headers */ d[1]=0; amid=[mdb midForId: c]; if (amid==0) { amid=[mdb createMessageWithId: c source: nil]; if (amid!=0) { *c=0; if (strchr(buf,'<')) [mdb msg_setMetaHeader: "Real-References" value: buf : amid]; } } if (amid!=0) { after=[self indexOf: amid]; if (after==-1) { [self addMsg: amid]; after=[self indexOf: amid]; } if (after!=-1) break; } } free(buf); } else after=-1; msgs=realloc(msgs,sizeof(msg_info_t)*(num_msgs+1)); if (!msgs) abort(); { int i,l; if (after==-1) { after=num_msgs-1; l=0; } else { l=msgs[after].level+1; while (after=l) after++; } for (i=num_msgs;i>after+1;i--) msgs[i]=msgs[i-1]; m=&msgs[after+1]; m->level=l; m->cached=0; /* now adjust the rows in the header cache entries */ for (i=0;iafter) header_cache[i].row++; } } { const char *c=[mdb msg_getMetaHeader: "RStatus" : mid]; if (c) m->rstatus=atoi(c); else m->rstatus=0; } m->mid=mid; num_msgs++; adding_stack_num--; return after+1; } /*-(void) addMsg: (msg_id_t)mid { msg_info_t *m; if ([self indexOf: mid]!=-1) return; msgs=realloc(msgs,sizeof(msg_info_t)*(num_msgs+1)); if (!msgs) abort(); m=&msgs[num_msgs]; { const char *c=[mdb msg_getMetaHeader: "RStatus" : mid]; if (c) m->rstatus=atoi(c); else m->rstatus=0; } m->mid=mid; num_msgs++; [list reloadData]; }*/ -(BOOL) selectFirstUnreadFrom: (int)i { for (;i0;i--) if (msgs[i].level<=max) break; return i; } -(void) markAsRead: (int)f : (int)to; { if (f<0) f=0; for (;f<=to;f++) { if (msgs[f].rstatus!=1) [mdb msg_setMetaHeader: "RStatus" value: "1" : msgs[f].mid]; } } -(NSString *) header: (const char *)h forMessage: (int)msg { const char *c; NSData *d; NSString *s2; c=[mdb msg_getHeader: h : msgs[msg].mid]; if (!c) return @""; d=[[NSData alloc] initWithBytes: c length: strlen(c)]; s2=[MimeUtility decodeHeader: d charset: nil]; [d release]; return s2; } -(void) _fixCache: (int)msg { header_cache_t *h; int index; if (msgs[msg].cached) { cache_hits++; return; } cache_misses++; index=header_cache[0].prev; h=&header_cache[index]; if (h->row!=-1) { DESTROY(h->subject); DESTROY(h->from); DESTROY(h->date); msgs[h->row].cached=0; } h->row=msg; msgs[msg].cached=index; [self _moveFirst: index]; { const char *c; NSString *s2; NSData *d; c=[mdb msg_getHeader: "Subject" : msgs[msg].mid]; if (!c) h->subject=[[NSString alloc] initWithCString: [mdb msg_getMessageID: msgs[msg].mid]]; else { d=[[NSData alloc] initWithBytes: c length: strlen(c)]; s2=[MimeUtility decodeHeader: d charset: nil]; [d release]; h->subject=[s2 retain]; } c=[mdb msg_getHeader: "From" : msgs[msg].mid]; if (c) { d=[[NSData alloc] initWithBytes: c length: strlen(c)]; s2=[MimeUtility decodeHeader: d charset: nil]; [d release]; h->from=[s2 retain]; } else h->from=@""; c=[mdb msg_getHeader: "Date" : msgs[msg].mid]; if (c) h->date=parse_date(c); else h->date=nil; } [self _sanity]; } -(NSString *) subjectForMessage: (int)msg { // if (!msgs[msg].cached) [self _fixCache: msg]; return AUTORELEASE(RETAIN(header_cache[msgs[msg].cached].subject)); } -(NSString *) fromForMessage: (int)msg { // if (!msgs[msg].cached) [self _fixCache: msg]; return AUTORELEASE(RETAIN(header_cache[msgs[msg].cached].from)); } -(NSDate *) dateForMessage: (int)msg { // if (!msgs[msg].cached) [self _fixCache: msg]; return AUTORELEASE(RETAIN(header_cache[msgs[msg].cached].date)); } @end LuserNET-0.4.2/FolderWindowController.h100664 764 764 1525 10021217655 16157 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef FolderWindowController_h #define FolderWindowController_h #include @class MsgDB; @class NSString,NSTableView,NSTableColumn,NSScrollView; @class MessageViewController,FolderThreader; /* TODO: declare instead */ #include "MsgDB.h" @interface FolderWindowController : NSWindowController { NSString *folder_name; MsgDB *mdb; NSObject *folder; FolderThreader *ft; MessageViewController *mv; NSTableView *list; NSTableColumn *c_subject,*c_date,*c_from; NSScrollView *listsv; int sort_mode; NSMutableArray *sorted_msgs; msg_id_t cur_selection; /* this size must match the stuff in the readahead category */ msg_id_t cur_read_ahead[4]; } - initWithMsgDB: (MsgDB *)m folder: (NSString *)fname; @end #endif LuserNET-0.4.2/FolderWindowController.m100664 764 764 54077 10021217655 16216 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MsgDB.h" #include "FolderWindowController.h" #include "FolderThreader.h" #include "KeyWindow.h" #include "MessageViewController.h" #include "main.h" #include "Pref_ReadAhead.h" #define SORT_THREAD 0 #define SORT_R_THREAD 1 #define SORT_SUBJECT 2 #define SORT_FROM 3 #define SORT_DATE 4 #define SORT_ORDER 5 #define SORT_R_SUBJECT 6 #define SORT_R_FROM 7 #define SORT_R_DATE 8 #define SORT_R_ORDER 9 #define SORT_MAX 9 static int sort_subject(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { NSString *s1,*s2; s1=[ft subjectForMessage: [n1 unsignedIntValue]]; s2=[ft subjectForMessage: [n2 unsignedIntValue]]; return [s1 localizedCaseInsensitiveCompare: s2]; } static int sort_from(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { NSString *s1,*s2; s1=[ft fromForMessage: [n1 unsignedIntValue]]; s2=[ft fromForMessage: [n2 unsignedIntValue]]; return [s1 localizedCaseInsensitiveCompare: s2]; } static int sort_date(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { NSDate *d1,*d2; d1=[ft dateForMessage: [n1 unsignedIntValue]]; d2=[ft dateForMessage: [n2 unsignedIntValue]]; return [d1 compare: d2]; } static int sort_r_subject(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { return -sort_subject(n1,n2,ft); } static int sort_r_from(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { return -sort_from(n1,n2,ft); } static int sort_r_date(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { return -sort_date(n1,n2,ft); } static int sort_order(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { if (ft->msgs[[n1 unsignedIntValue]].midmsgs[[n2 unsignedIntValue]].mid) return NSOrderedAscending; else return NSOrderedDescending; } static int sort_r_order(NSNumber *n1,NSNumber *n2,FolderThreader *ft) { if (ft->msgs[[n1 unsignedIntValue]].mid>ft->msgs[[n2 unsignedIntValue]].mid) return NSOrderedAscending; else return NSOrderedDescending; } static int (*(sort_funcs[SORT_MAX+1]))(NSNumber *,NSNumber *,FolderThreader *ft)= {NULL,NULL, sort_subject,sort_from,sort_date,sort_order, sort_r_subject,sort_r_from,sort_r_date,sort_r_order}; @interface IndentCell : NSCell { int level; } -(void) setLevel: (int)l; @end @implementation IndentCell -(void) setLevel: (int)l { level=l; } -(void) drawWithFrame: (NSRect)f inView: (NSView *)v { if (level>8) { NSString *s=[[NSString alloc] initWithFormat: @"%i",level]; [s drawInRect: f withAttributes: nil]; DESTROY(s); } f.origin.x+=level*16; f.size.width-=level*16; [super drawWithFrame: f inView: v]; } @end #define RA_NEXT_UNREAD 0 #define RA_NEXT 1 #define RA_PREV 2 #define RA_NEXT_THREAD 3 #define RA_MAX 4 #define RA_NONE 4 /* TODO: read-ahead to a greater depth? (ie. use the 'weight'; get the two next unread, then three, etc. */ @interface FolderWindowController (readahead) -(void) readAheadForMessage: (int)num; -(void) cancelCurrentReadAhead; -(void) readAheadWeight: (int)which; @end @implementation FolderWindowController (readahead) static float ra_priority[RA_MAX]; -(void) _readAheadMid: (msg_id_t)mid : (int)limit : (int)pri; { const char *c; int size; int status; c=[mdb msg_getHeader: "Bytes" : mid]; if (!c) return; size=atoi(c); if (!size || size>limit) { return; } status=[mdb msg_dstatus: mid]; if (status==DSTATUS_ERROR) return; [mdb msg_wantData: mid priority: pri]; } /* TODO: more options */ -(void) readAheadForMessage: (int)num { int limit; int i; Class p=[Pref_ReadAhead class]; if (![Pref_ReadAhead readAhead]) return; limit=[Pref_ReadAhead readAheadSizeLimit]; for (i=0;imsgs[s].mid; } if ([p readAheadNextThread]) { /* next thread */ s=[ft findNextMax: 0 from: num+1]; if (s!=ft->num_msgs) { s=[ft findNextUnreadFrom: s]; if (s!=-1) cur_read_ahead[RA_NEXT_THREAD]=ft->msgs[s].mid; } } s=num; if ([p readAheadPrevious]) { /* previous message */ if (s>0) cur_read_ahead[RA_PREV]=ft->msgs[s-1].mid; } if ([p readAheadNext]) { /* next message */ if (snum_msgs-1) cur_read_ahead[RA_NEXT]=ft->msgs[s+1].mid; } } else { if ([p readAheadPrevious] && num>0) { cur_read_ahead[RA_PREV]= ft->msgs[[[sorted_msgs objectAtIndex: num-1] unsignedIntValue]].mid; } if ([p readAheadNext] && num<[sorted_msgs count]-1) { cur_read_ahead[RA_NEXT]= ft->msgs[[[sorted_msgs objectAtIndex: num+1] unsignedIntValue]].mid; } } /* TODO: if eg. next unread and next are the same, the max of their priority should be used */ for (i=0;i3) ra_priority[i]=3; } else { ra_priority[i]-=1.0; if (ra_priority[i]<-5) ra_priority[i]=-5; } } } @end @interface FolderWindowController (private) -(int) indexOf: (msg_id_t) mid; -(void) selectMid: (msg_id_t) mid; -(msg_id_t) selectedMid; -(void) setSortMode: (int)ns; -(void) fixSort; @end @implementation FolderWindowController (private) -(void) selectMessage: (int)num { if (num<0 || num>=ft->num_msgs) return; [list selectRow: num byExtendingSelection: NO]; if (num<5) [list scrollRowToVisible: 0]; else [list scrollRowToVisible: num-5]; if (num+5num_msgs) [list scrollRowToVisible: num+5]; else [list scrollRowToVisible: ft->num_msgs-1]; [list scrollRowToVisible: num]; } -(msg_id_t) selectedMid { int s=[list selectedRow]; if (s==-1) return 0; if (sort_mode!=SORT_THREAD) { [self fixSort]; s=[[sorted_msgs objectAtIndex: s] unsignedIntValue]; } return ft->msgs[s].mid; } -(int) indexOf: (msg_id_t)mid { int r; int i,c; r=[ft indexOf: mid]; if (r==-1) return -1; if (sort_mode==SORT_THREAD) return r; [self fixSort]; c=[sorted_msgs count]; for (i=0;inum_msgs; sorted_msgs=[[NSMutableArray alloc] initWithCapacity: n]; p=n; while (p>0) { np=p-1; for (p=np;p>=0;p--) if (!ft->msgs[p].level) break; if (p<0) p=0; for (i=p;i<=np;i++) { idx=[[NSNumber alloc] initWithUnsignedInt: i]; [sorted_msgs addObject: idx]; [idx release]; } } } else { if (!sorted_msgs) { int i,n; msg_id_t *msgs; NSNumber *idx; n=[folder numMessages]; msgs=[folder getMessages]; sorted_msgs=[[NSMutableArray alloc] initWithCapacity: n]; for (i=0;imsgs[idx].rstatus) { ft->msgs[idx].rstatus=nrstatus; [list reloadData]; } return; } if ([[n name] isEqual: MsgDB_MsgMetaChangeNotification] && (idx=[ft indexOf: [n mid]])!=-1) { [list reloadData]; /* TODO: only need reload if the message is visible */ return; } } -(int) numberOfRowsInTableView: (NSTableView *)tv { if (sort_mode==SORT_THREAD) return ft->num_msgs; else { if (sorted_msgs) return [sorted_msgs count]; if (sort_mode==SORT_R_THREAD) return ft->num_msgs; else return [folder numMessages]; } } -(id) tableView: (NSTableView *)tv objectValueForTableColumn: (NSTableColumn *)tc row: (int)row { if (sort_mode!=SORT_THREAD) { if (!sorted_msgs) [self fixSort]; row=[[sorted_msgs objectAtIndex: row] unsignedIntValue]; } if (tc==c_subject) return [ft subjectForMessage: row]; else if (tc==c_from) return [ft fromForMessage: row]; else if (tc==c_date) { NSDate *d=[ft dateForMessage: row]; NSTimeInterval ti=[d timeIntervalSinceNow]; NSString *frmt; if (ti>0) /* TODO: make this customizable? */ return d; else if (ti>-18*6*60) frmt=@"%H:%M:%S"; else if (ti>-(6*24+18)*60*60) frmt=@"%a %H:%M:%S"; else return d; return [d descriptionWithCalendarFormat: frmt timeZone: nil locale: nil]; } else return @"Unknown column"; } -(void) tableView: (NSTableView *)tv willDisplayCell: (NSCell *)c forTableColumn: (NSTableColumn *)tc row: (int)row { if (sort_mode!=SORT_THREAD) { if (!sorted_msgs) [self fixSort]; row=[[sorted_msgs objectAtIndex: row] unsignedIntValue]; } if (ft->msgs[row].rstatus) [c setFont: [NSFont systemFontOfSize: 0]]; else [c setFont: [NSFont boldSystemFontOfSize: 0]]; if (tc==c_subject) { if (sort_mode==SORT_THREAD || sort_mode==SORT_R_THREAD) [(IndentCell *)c setLevel: ft->msgs[row].level]; else [(IndentCell *)c setLevel: 0]; } } -(void) folderSortThread { [self setSortMode: SORT_THREAD]; } -(void) folderSortReverseThread { [self setSortMode: SORT_R_THREAD]; } -(void) folderSortSubject { [self setSortMode: SORT_SUBJECT]; } -(void) folderSortReverseSubject { [self setSortMode: SORT_R_SUBJECT]; } -(void) folderSortFrom { [self setSortMode: SORT_FROM]; } -(void) folderSortReverseFrom { [self setSortMode: SORT_R_FROM]; } -(void) folderSortDate { [self setSortMode: SORT_DATE]; } -(void) folderSortReverseDate { [self setSortMode: SORT_R_DATE]; } -(void) folderSortOrder { [self setSortMode: SORT_ORDER]; } -(void) folderSortReverseOrder { [self setSortMode: SORT_R_ORDER]; } - initWithMsgDB: (MsgDB *)m folder: (NSString *)fname { NSWindow *win; win=[[KeyWindow alloc] initWithContentRect: NSMakeRect(100,100,550,500) styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask backing: NSBackingStoreRetained defer: YES]; if (!(self=[super initWithWindow: win])) return nil; folder=[[m folders] objectForKey: fname]; if (!folder) { RELEASE(self); return nil; } [folder retain]; ASSIGN(mdb,m); folder_name=[fname copy]; ft=[[FolderThreader alloc] initWithMsgDB: mdb target: self]; { NSSplitView *spv; spv=[[NSSplitView alloc] initWithFrame: NSMakeRect(0,0,550,500)]; [spv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; { [win setTitle: folder_name]; c_subject=[[NSTableColumn alloc] initWithIdentifier: @"Subject"]; [[c_subject headerCell] setStringValue: _(@"Subject")]; [c_subject setEditable: NO]; [c_subject setResizable: YES]; [c_subject setWidth: 300]; { IndentCell *c=[[IndentCell alloc] init]; [c_subject setDataCell: c]; [c release]; } c_from=[[NSTableColumn alloc] initWithIdentifier: @"From"]; [[c_from headerCell] setStringValue: _(@"From")]; [c_from setEditable: NO]; [c_from setResizable: YES]; [c_from setWidth: 100]; c_date=[[NSTableColumn alloc] initWithIdentifier: @"Date"]; [[c_date headerCell] setStringValue: _(@"Date")]; [c_date setEditable: NO]; [c_date setResizable: YES]; [c_date setWidth: 100]; listsv=[[NSScrollView alloc] initWithFrame: NSMakeRect(0,0,250,250)]; /* TODO */ [listsv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [listsv setAutoresizesSubviews: YES]; [listsv setHasVerticalScroller: YES]; [listsv setHasHorizontalScroller: YES]; [listsv setBorderType: NSBezelBorder]; list=[[NSTableView alloc] initWithFrame: [[listsv contentView] frame]]; [list setAllowsColumnReordering: YES]; [list setAllowsColumnResizing: YES]; [list setAllowsMultipleSelection: NO]; [list setAllowsColumnSelection: NO]; [list addTableColumn: c_subject]; [list addTableColumn: c_from]; [list addTableColumn: c_date]; [list setDataSource: self]; [list setDelegate: self]; [list setDoubleAction: @selector(openMessage:)]; [list setAutosaveName: [NSString stringWithFormat: @"Folder_%@",fname]]; [list setAutosaveTableColumns: YES]; [listsv setDocumentView: list]; [spv addSubview: listsv]; } { /* TODO: fix properly */ NSScrollView *sv; NSTextView *tv; sv=[[NSScrollView alloc] initWithFrame: NSMakeRect(0,0,250,250)]; [sv setHasVerticalScroller: YES]; [sv setHasHorizontalScroller: YES]; [sv setBorderType: NSBezelBorder]; tv=[[NSTextView alloc] initWithFrame: [[sv contentView] frame]]; [tv setHorizontallyResizable: NO]; [tv setVerticallyResizable: YES]; [tv setEditable: NO]; [[tv textContainer] setWidthTracksTextView: YES]; [[tv textContainer] setHeightTracksTextView: NO]; [[tv textContainer] setContainerSize: NSMakeSize(1e6,1e6)]; [tv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [sv setDocumentView: tv]; [spv addSubview: sv]; mv=[[MessageViewController alloc] initWithMsgDB: mdb textView: tv scrollView: sv]; [sv release]; [tv release]; } [win setContentView: spv]; [spv release]; { NSString *frame_name=[NSString stringWithFormat: @"Folder_%@",fname]; [win setFrameUsingName: frame_name]; [win setFrameAutosaveName: frame_name]; } } [win setDelegate: self]; [win release]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateFolder:) name: MsgDB_FolderAddMsgNotification object: mdb]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateFolder:) name: MsgDB_MsgMetaChangeNotification object: mdb]; { int n,i; msg_id_t *m; n=[folder numMessages]; m=[folder getMessages]; for (i=0;iSORT_MAX) ns=SORT_THREAD; [self setSortMode: ns]; } return self; } -(void) dealloc { if (folder_name) { [[NSUserDefaults standardUserDefaults] setInteger: sort_mode forKey: [NSString stringWithFormat: @"FolderSortMode_%@",folder_name]]; } [[NSNotificationCenter defaultCenter] removeObserver: self]; DESTROY(listsv); DESTROY(list); DESTROY(c_subject); DESTROY(c_date); DESTROY(c_from); DESTROY(mv); DESTROY(ft); DESTROY(sorted_msgs); DESTROY(folder); DESTROY(folder_name); DESTROY(mdb); [super dealloc]; } -(void) tableViewSelectionDidChange: (NSNotification *)n { [self cancelCurrentReadAhead]; [mv setMid: [self selectedMid]]; [self readAheadForMessage: [self indexOf: [self selectedMid]]]; /* TODO */ } -(void) openMessage: (id)sender { if ([self selectedMid]) [app_delegate openMessageWindow: [self selectedMid]]; } -(void) messageToggleRead: (id)sender { int selected=[list selectedRow]; if (selected==-1) return; if (sort_mode!=SORT_THREAD) { [self fixSort]; selected=[[sorted_msgs objectAtIndex: selected] unsignedIntValue]; } if (ft->msgs[selected].rstatus==1) [mdb msg_setMetaHeader: "RStatus" value: "0" : ft->msgs[selected].mid]; else [mdb msg_setMetaHeader: "RStatus" value: "1" : ft->msgs[selected].mid]; } -(void) moveToNextUnread: (id)sender { int s=[list selectedRow]; [self readAheadWeight: RA_NEXT_UNREAD]; if (sort_mode==SORT_THREAD) [ft selectFirstUnreadFrom: s+1]; /* this is valid even if s==-1 */ else { int i,c; [self fixSort]; c=[sorted_msgs count]; for (i=s+1;imsgs[ [[sorted_msgs objectAtIndex: i] unsignedIntValue] ].rstatus) { [self selectMessage: i]; break; } } } -(void) scrollNextUnread: (id)sender { if ([mv scrollDown]) [self moveToNextUnread: nil]; } -(void) skipBranch: (id)sender { if (sort_mode==SORT_THREAD) { int s=[list selectedRow]; if (s==-1) return; s=[ft findNextMax: ft->msgs[s].level from: s+1]; [ft selectFirstUnreadFrom: s]; } } -(void) skipBranchMark: (id)sender { if (sort_mode==SORT_THREAD) { int s=[list selectedRow],n; if (s==-1) return; n=[ft findNextMax: ft->msgs[s].level from: s+1]; [ft markAsRead: s:n-1]; [ft selectFirstUnreadFrom: n]; } } -(void) skipThread: (id)sender { [self readAheadWeight: RA_NEXT_THREAD]; if (sort_mode==SORT_THREAD) { int s=[list selectedRow]; if (s==-1) return; s=[ft findNextMax: 0 from: s+1]; [ft selectFirstUnreadFrom: s]; } } -(void) skipThreadMark: (id)sender { [self readAheadWeight: RA_NEXT_THREAD]; if (sort_mode==SORT_THREAD) { int s=[list selectedRow],n,p; if (s==-1) return; n=[ft findNextMax: 0 from: s+1]; p=[ft findPrevMax: 0 from: s]; [ft markAsRead: p:n-1]; [ft selectFirstUnreadFrom: n]; } } -(void) moveToParent: (id)sender { if (sort_mode==SORT_THREAD) { int s=[list selectedRow]; if (s==-1) return; if (!ft->msgs[s].level) return; [self selectMessage: [ft findPrevMax: ft->msgs[s].level-1 from: s-1]]; } } -(void) markAll: (id)sender { int i; for (i=0;inum_msgs;i++) { if (ft->msgs[i].rstatus) continue; [mdb msg_setMetaHeader: "RStatus" value: "1" : ft->msgs[i].mid]; } } -(int) keyDown: (NSEvent *)ke inWindow: (NSWindow *)w { NSString *str=[ke charactersIgnoringModifiers]; unichar c; if ([str length]!=1) return 0; c=[str characterAtIndex: 0]; if (!([ke modifierFlags]&~NSShiftKeyMask) || [ke modifierFlags]==NSFunctionKeyMask) { int s=[list selectedRow]; switch (c) { case NSUpArrowFunctionKey: case 'p': if (s>0) { [self readAheadWeight: RA_PREV]; [self selectMessage: s-1]; } return 1; case NSDownArrowFunctionKey: if (snum_msgs-1) { [self readAheadWeight: RA_NEXT]; [self selectMessage: s+1]; } return 1; case NSHomeFunctionKey: if (ft->num_msgs) [self selectMessage: 0]; return 1; case NSEndFunctionKey: if (ft->num_msgs) [self selectMessage: ft->num_msgs-1]; return 1; case NSPageDownFunctionKey: if (s==-1) [self selectMessage: 0]; else { s=s+floor(([[listsv contentView] frame].size.height/[list rowHeight])); s--; if (s>=ft->num_msgs) s=ft->num_msgs-1; /* might happen if the window is really small (less than one row visible */ if (s<0) s=0; [self selectMessage: s]; } return 1; case NSPageUpFunctionKey: if (s==-1) [self selectMessage: 0]; else { s=s-floor(([[listsv contentView] frame].size.height/[list rowHeight])); s++; if (s<0) s=0; if (s>=ft->num_msgs) s=ft->num_msgs-1; [self selectMessage: s]; } return 1; case 's': [mv lineUp]; return 1; case 'x': [mv lineDown]; return 1; case ' ': [self scrollNextUnread: nil]; return 1; case 'n': [self moveToNextUnread: nil]; return 1; case 'b': if (sort_mode!=SORT_THREAD) return 0; [self skipBranch: nil]; return 1; case 't': if (sort_mode!=SORT_THREAD) return 0; [self skipThread: nil]; return 1; case 'B': if (sort_mode!=SORT_THREAD) return 0; [self skipBranchMark: nil]; return 1; case 'T': if (sort_mode!=SORT_THREAD) return 0; [self skipThreadMark: nil]; return 1; case 'P': if (sort_mode!=SORT_THREAD) return 0; [self moveToParent: nil]; return 1; case 'm': [self messageToggleRead: nil]; return 1; case 'A': [self markAll: nil]; return 1; } } return 0; } -(void) composeNewArticle: (id)sender { [app_delegate composeArticle: [NSDictionary dictionaryWithObject: folder_name forKey: @"Newsgroups"]]; } /* Same as in PreferencesWindowController. Let the MessageViewController act as a delegate for messages that it's interested in. */ -(BOOL) respondsToSelector: (SEL)s { if ([super respondsToSelector: s]) return YES; return [mv respondsToSelector: s]; } -(void) forwardInvocation: (NSInvocation *)i { if ([mv respondsToSelector: [i selector]]) { [i invokeWithTarget: mv]; return; } [super forwardInvocation: i]; } -(NSMethodSignature *) methodSignatureForSelector: (SEL)sel { NSMethodSignature *ms; ms=[super methodSignatureForSelector: sel]; if (ms) return ms; ms=[mv methodSignatureForSelector: sel]; return ms; } @end LuserNET-0.4.2/GNUmakefile100664 764 764 1722 10021220126 13373 0ustar alexalexinclude $(GNUSTEP_MAKEFILES)/common.make APP_NAME = LuserNET include VERSION PACKAGE_NAME = LuserNET CVS_OPTIONS = -d/opt/cvsroot CVS_MODULE_NAME = news ADDITIONAL_OBJCFLAGS = -Wall -O2 -g LuserNET_OBJC_FILES = \ NNTPServer.m \ MsgDB.m \ NNTPSource.m \ \ KeyWindow.m \ \ NNTPSourceGUI.m \ \ main.m \ main_prefs.m \ \ FolderListController.m \ FolderWindowController.m \ FolderThreader.m \ LogWindowController.m \ MessageViewController.m \ ComposeWindowController.m \ \ PreferencesWindowController.m \ Pref_Sources.m \ Pref_MessageViewing.m \ Pref_ReadAhead.m \ Pref_Posting.m \ \ autokeyviewchain.m LuserNET_APPLICATION_ICON = News.app.tiff LuserNET_RESOURCE_FILES = News.app.tiff LuserNET_LOCALIZED_RESOURCE_FILES = Localizable.strings LuserNET_LANGUAGES = English Swedish Spanish French German MAKE_STRINGS_OPTIONS = --aggressive-match --aggressive-remove LuserNET_LDFLAGS += -lPantomime #-lMime include $(GNUSTEP_MAKEFILES)/application.make LuserNET-0.4.2/GUISource.h100664 764 764 507 7434300533 13256 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef GUISource_h #define GUISource_h /* Things a MsgDB_Source can implement so it can be configured easily in the preferences panel. */ @protocol GUISource -(NSString *) sourceName; -(NSString *) sourceType; -(void) displayPropertiesWindow; @end #endif LuserNET-0.4.2/KeyWindow.h100664 764 764 542 7433715602 13376 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef KeyWindow_h #define KeyWindow_h /* window that passes key-events to its delegate before trying to handle them */ #include @protocol KeyWindowDelegate -(int) keyDown: (NSEvent *)e inWindow: (NSWindow *)w; @end @interface KeyWindow : NSWindow @end #endif LuserNET-0.4.2/KeyWindow.m100664 764 764 567 7433715602 13412 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include "KeyWindow.h" @implementation KeyWindow -(void) keyDown: (NSEvent *)e { if ([self delegate]) if ([[self delegate] respondsToSelector: @selector(keyDown:inWindow:)]) if ([[self delegate] keyDown: e inWindow: self]) return; [super keyDown: e]; } @end LuserNET-0.4.2/LogWindowController.h100664 764 764 503 7433743222 15427 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef LogWindowController_h #define LogWindowController_h #include @class NSTextView; @interface LogWindowController : NSWindowController { NSTextView *log; } - init; -(void) addLogString: (NSString *)s; @end #endif LuserNET-0.4.2/LogWindowController.m100664 764 764 4173 10021217655 15474 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include "LogWindowController.h" @implementation LogWindowController - init { NSWindow *win; win=[[NSWindow alloc] initWithContentRect: NSMakeRect(100,100,500,200) styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask backing: NSBackingStoreRetained defer: YES]; if (!(self=[super initWithWindow: win])) return nil; { /* TODO: fix properly */ NSScrollView *sv; // NSRect r; // r=NSMakeRect(0,0,500,200); [win setTitle: _(@"Log")]; // sv=[[NSScrollView alloc] initWithFrame: r]; sv=[[NSScrollView alloc] init]; // [sv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; // [sv setAutoresizesSubviews: YES]; [sv setHasVerticalScroller: YES]; [sv setHasHorizontalScroller: NO]; [sv setBorderType: NSBezelBorder]; log=[[NSTextView alloc] initWithFrame: [[sv contentView] frame]]; // log=[[NSTextView alloc] init]; [log setHorizontallyResizable: NO]; [log setVerticallyResizable: YES]; [log setEditable: NO]; [[log textContainer] setWidthTracksTextView: YES]; [[log textContainer] setHeightTracksTextView: NO]; [[log textContainer] setContainerSize: NSMakeSize(0,1e6)]; [log setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [sv setDocumentView: log]; [win setContentView: sv]; [sv release]; } [win setFrameUsingName: @"LogWindow"]; [win setFrameAutosaveName: @"LogWindow"]; [win release]; return self; } -(void) dealloc { DESTROY(log); [super dealloc]; } -(void) addLogString: (NSString *)s { NSTextStorage *ts; int l; ts=[log textStorage]; [ts beginEditing]; l=[ts length]; [ts replaceCharactersInRange: NSMakeRange(l,0) withString: s]; l+=[s length]; [ts replaceCharactersInRange: NSMakeRange(l,0) withString: @"\n"]; [ts endEditing]; [log scrollRangeToVisible: NSMakeRange(l-1,1)]; } @end LuserNET-0.4.2/LuserNETInfo.plist100664 764 764 466 7442430312 14634 0ustar alexalex{ ApplicationDescription = "News reader"; Authors = ("Alexander Malmberg "); Copyright = "Copyright 2002 Alexander Malmberg (icon by Andrew Lindesay)"; CopyrightDescription = "GNU General Public License"; URL = "http://w1.423.telia.com/~u42308495/alex/LuserNET/LuserNET.html"; } LuserNET-0.4.2/MessageViewController.h100664 764 764 1445 7442004106 15752 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef MessageViewController_h #define MessageViewController_h @class NSTextView,NSScrollView,NSMutableDictionary; @class Message; #include "MsgDB.h" @interface MessageViewController : NSObject { NSTextView *tv; NSScrollView *sv; MsgDB *mdb; msg_id_t mid; Message *cur_message; /* actually an NSMapTable */ void *cur_options; int cur_font; int show_source; } - initWithMsgDB: (MsgDB *)m textView: (NSTextView *)textview scrollView: (NSScrollView *)scrollview; -(void) setMid: (msg_id_t)m; -(BOOL) atEnd; /* returns YES if we hit the end of the message */ -(BOOL) scrollDown; -(void) lineUp; -(void) lineDown; -(void) switchFont; -(void) showSource; -(void) messageDownload; -(void) messageSave; @end #endif LuserNET-0.4.2/MessageViewController.m100664 764 764 57563 10021217655 16035 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include #include #include "MessageViewController.h" #include "Pref_MessageViewing.h" #include "main.h" #include "GUISource.h" #include #include #include #include #include //#define USE_MIME_STUFF #ifdef USE_MIME_STUFF #include static MimeManager *mime_manager; #endif static int get_depth(NSString *t,int start,int end) { int i; int nd; unichar uc; for (i=start,nd=0;i') nd++; else if (uc>32) break; } return nd; } /* we intentionally do _not_ retain object */ @interface MessageViewController_Link : NSObject { #define LINK_SOURCE 1 #define LINK_MULTIPART_ALTERNATIVE 2 #define LINK_DATA_SHOW 3 #define LINK_DATA_SHOW_TEXT 4 #define LINK_DATA_SAVE 5 #define LINK_DOWNLOAD 6 @public int type; id object; int value; } + linkWithType: (int)t object: (id)o value: (int)v; - initWithType: (int)t object: (id)o value: (int)v; @end @implementation MessageViewController_Link + linkWithType: (int)t object: (id)o value: (int)v { return [[[self alloc] initWithType: t object: o value: v] autorelease]; } - initWithType: (int)t object: (id)o value: (int)v { self=[super init]; type=t; object=o; value=v; return self; } @end @interface MessageViewController (private) -(void) _renderContent:(id)p parent:(Part *)parent to:(NSMutableAttributedString *)str; -(void) _renderPart:(id)p to:(NSMutableAttributedString *)str; -(void) displayMessage; -(void) getData; @end @implementation MessageViewController (private) static NSDictionary *colorForDepth(int d) { #define MAX_DEPTH 6 static NSDictionary *dicts[MAX_DEPTH]; if (d) { d=((d-1)%(MAX_DEPTH-1))+1; } if (!dicts[d]) { NSColor *c; switch (d) { default: case 0: c=[NSColor blackColor]; break; case 1: c=[NSColor blueColor]; break; case 2: c=[NSColor brownColor]; break; case 3: c=[NSColor purpleColor]; break; case 4: c=[NSColor orangeColor]; break; case 5: c=[NSColor redColor]; break; } dicts[d]=[[NSDictionary alloc] initWithObjectsAndKeys: c,NSForegroundColorAttributeName,nil]; } return dicts[d]; } static NSDictionary *header_bold_dict,*header_dict,*header_link_dict; /* helpers for _render* */ static void append(NSMutableAttributedString *str,NSDictionary *attrs, NSString *format,...) { va_list args; NSAttributedString *as; NSString *s; va_start(args,format); s=[[NSString alloc] initWithFormat:format arguments:args]; va_end(args); as=[[NSAttributedString alloc] initWithString:s attributes:attrs]; [str appendAttributedString: as]; DESTROY(as); DESTROY(s); } static void appendLink(NSMutableAttributedString *str,int type,id object, int value,NSString *format, ...) { va_list args; NSMutableAttributedString *as; NSString *s; va_start(args,format); s=[[NSString alloc] initWithFormat: format arguments: args]; va_end(args); as=[[NSMutableAttributedString alloc] initWithString:s attributes:header_link_dict]; [as addAttribute: NSLinkAttributeName value: [MessageViewController_Link linkWithType: type object: object value: value] range: NSMakeRange(0,[as length])]; [str appendAttributedString: as]; DESTROY(as); DESTROY(s); } static void appendPlainText(NSMutableAttributedString *str, NSDictionary *fd,NSString *text) { int len; len=[str length]; [str replaceCharactersInRange: NSMakeRange(len,0) withString: text]; [str setAttributes: fd range: NSMakeRange(len,[text length])]; if ([Pref_MessageViewing colorMessages]) { int i,j,d; for (i=j=0;i<[text length];i++) { if ([text characterAtIndex: i]=='\n') { if (i>j) { d=get_depth(text,j,i); /*if (d)*/ [str addAttributes: colorForDepth(d) range: NSMakeRange(len+j,i-j)]; } j=i+1; } } if (i>j) { d=get_depth(text,j,i); /*if (d)*/ [str addAttributes: colorForDepth(d) range: NSMakeRange(len+j,i-j)]; } } append(str,nil,@"\n"); } -(void) _render_multipart: (MimeMultipart *)mp type: (NSString *)ct to: (NSMutableAttributedString *)str { Part *bp; int i; if ([ct isEqual: @"multipart/alternative"]) { int disp; if (cur_options && (disp=(int)NSMapGet(cur_options,mp))) disp--; else disp=[mp count]-1; bp=[mp bodyPartAtIndex: disp]; append(str,header_bold_dict,_(@"Currently shown:")); append(str,header_dict,@" %@\n",[bp contentType]); append(str,header_bold_dict,_(@"Alternatives:")); for (i=0;i<[mp count];i++) { appendLink(str,LINK_MULTIPART_ALTERNATIVE,mp,i+1,@" %@", [[mp bodyPartAtIndex: i] contentType]); } append(str,nil,@"\n"); [self _renderPart: bp to:str]; } else { /* handle multipart/mixed and everything we don't understand */ int i; if (![ct isEqual: @"multipart/mixed"]) append(str,header_bold_dict,_(@"Displaying multipart with %i parts: %@\n"), [mp count],ct); for (i=0;i<[mp count];i++) { bp=[mp bodyPartAtIndex: i]; append(str,header_bold_dict,_(@"\nPart:")); append(str,header_dict,@" %i %@\n",i,[bp contentType]); [self _renderPart:bp to:str]; } } } -(void) _render_article_text: (NSString *)text to: (NSMutableAttributedString *)str { NSDictionary *fd; NSFont *f; NSRange r; if (cur_font) f=[Pref_MessageViewing font2]; else f=[Pref_MessageViewing font1]; fd=[[NSDictionary alloc] initWithObjectsAndKeys: f,NSFontAttributeName,nil]; #if 0 /* TODO: need to update with new Pantomime interface */ /* TODO: this should probably be optional */ r=[MimeUtility rangeOfUUEncodedStringFromString: text range: NSMakeRange(0,[text length])]; if (r.location==NSNotFound) #endif appendPlainText(str,fd,text); #if 0 else { NSFileWrapper *fw; if (r.location>0) appendPlainText(str,fd,[text substringWithRange: NSMakeRange(0,r.location)]); /* TODO: this is inefficient */ fw=[MimeUtility fileWrapperFromUUEncodedString: [text substringWithRange: r]]; append(str,header_bold_dict,_(@"UUEncoded file:")); append(str,header_dict,@" %@ ",[fw preferredFilename]); appendLink(str,LINK_DATA_SAVE,text,0,_(@"Save...")); append(str,header_dict,@"\n"); append(str,header_bold_dict,_(@"Size:")); append(str,header_dict,@" %i\n",[[fw regularFileContents] length]); DESTROY(fw); if (NSMaxRange(r)<[text length]) appendPlainText(str,fd,[text substringWithRange: NSMakeRange(NSMaxRange(r),[text length]-NSMaxRange(r))]); } #endif DESTROY(fd); } -(void) _renderContent:(id)p parent:(Part *)parent to:(NSMutableAttributedString *)str { NSString *ct=[parent contentType]; #ifdef USE_MIME_STUFF NSObject *mh; #endif NSData *d; NSString *s; int dbytes; /* Always display these if they are available. */ if ([parent contentDescription]) { append(str,header_bold_dict,_(@"Description:")); append(str,header_dict,@" %@\n",[parent contentDescription]); } if ([parent filename]) { append(str,header_bold_dict,_(@"Filename:")); append(str,header_dict,@" %@\n",[parent filename]); } /* Some content classes/types we handle internally. */ if ([p isKindOfClass: [MimeMultipart class]]) { [self _render_multipart: (MimeMultipart *)p type: ct to: str]; return; } if ([p isKindOfClass: [NSString class]] && [ct isEqual: @"text/plain"]) { [self _render_article_text: (NSString *)p to: str]; return; } if ([p isKindOfClass: [Part class]]) { [self _renderPart: p to: str]; return; } /* We don't know how to handle it, let all the mime stuff try. */ if ([p isKindOfClass: [NSString class]]) { s=(NSString *)p; d=nil; #ifdef USE_MIME_STUFF mh=[mime_manager mimeHandlerWithString: s type: ct]; #endif } else if ([p isKindOfClass: [NSData class]]) { s=nil; d=(NSData *)p; #ifdef USE_MIME_STUFF mh=[mime_manager mimeHandlerWithData: d type: ct]; #endif } else { append(str,header_bold_dict, _(@"Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n"), NSStringFromClass([p class]),ct); return; } #ifdef USE_MIME_STUFF AUTORELEASE(mh); #endif /* Common header and options for all types. */ append(str,header_bold_dict,_(@"Type:")); append(str,header_dict,@" %@\n",ct); if (d) { append(str,header_bold_dict,_(@"Size:")); append(str,header_dict,@" %i\n ",[d length]); } appendLink(str,LINK_DATA_SAVE,parent,0,_(@"Save...")); dbytes=0; if (d) { append(str,nil,@" "); if (cur_options && ((int)NSMapGet(cur_options,p))==1) { appendLink(str,LINK_DATA_SHOW,p,0,_(@"Hide raw data")); dbytes=[d length]; if (dbytes>1024) dbytes=1024; } else { appendLink(str,LINK_DATA_SHOW,p,0,_(@"Show raw data")); } } append(str,nil,@" "); if (cur_options && ((int)NSMapGet(cur_options,p))==2) { appendLink(str,LINK_DATA_SHOW_TEXT,p,0,_(@"Hide text")); } else { appendLink(str,LINK_DATA_SHOW_TEXT,p,0,_(@"Try to show as text")); } append(str,nil,@"\n"); if (cur_options) { if (dbytes && d) { int i; NSData *d=(NSData *)p; const unsigned char *b; NSDictionary *fd; fd=[NSDictionary dictionaryWithObjectsAndKeys: [NSFont userFixedPitchFontOfSize: 0],NSFontAttributeName,nil]; b=[d bytes]; for (i=0;i)[s objectAtIndex: i] sourceName], [(id)[s objectAtIndex: i] sourceType]); } } } [ms endEditing]; } } @end @implementation MessageViewController -(BOOL) textView: (NSTextView *)t clickedOnLink: (MessageViewController_Link *)l atIndex: (unsigned int)idx { if (![l isKindOfClass: [MessageViewController_Link class]]) return NO; if (l->type==LINK_SOURCE) { int i,c; NSArray *s; /* make sure the source still exists */ s=[mdb sources]; c=[s count]; for (i=0;iobject) { [mdb msg_setSource: l->object : mid]; [mdb msg_needData: mid]; return YES; } } NSBeep(); return YES; } if (l->type==LINK_MULTIPART_ALTERNATIVE) { if (!cur_options) return YES; /* shouldn't happen */ NSMapInsert(cur_options,l->object,(void *)l->value); [self displayMessage]; return YES; } if (l->type==LINK_DATA_SHOW) { if (!cur_options) return YES; if (((int)NSMapGet(cur_options,l->object))==1) NSMapRemove(cur_options,l->object); else NSMapInsert(cur_options,l->object,(void *)1); [self displayMessage]; return YES; } if (l->type==LINK_DATA_SHOW_TEXT) { if (!cur_options) return YES; if (((int)NSMapGet(cur_options,l->object))==2) NSMapRemove(cur_options,l->object); else NSMapInsert(cur_options,l->object,(void *)2); [self displayMessage]; return YES; } if (l->type==LINK_DATA_SAVE) { NSSavePanel *sp; int result; NSData *d; NSString *filename; if ([l->object isKindOf: [NSString class]]) { NSString *s=(NSString *)l->object; NSRange r; UUFile *fw; r=[MimeUtility rangeOfUUEncodedStringFromString: s range: NSMakeRange(0,[s length])]; fw=[MimeUtility fileFromUUEncodedString: [s substringWithRange: r]]; filename=AUTORELEASE(RETAIN([fw name])); d=AUTORELEASE(RETAIN([fw data])); } else { Part *p=(Part *)l->object; d=(NSData *)[p content]; filename=[p filename]; } sp=[NSSavePanel savePanel]; [sp setTitle: _(@"Save data")]; if (filename) { result=[sp runModalForDirectory: [[NSFileManager defaultManager] currentDirectoryPath] file: filename]; } else result=[sp runModal]; if (result!=NSOKButton) return YES; { FILE *f=fopen([[[sp filename] stringByStandardizingPath] fileSystemRepresentation],"wb"); if (!f) { NSBeep(); /* TODO */ return YES; } if (fwrite([d bytes],[d length],1,f)!=1) NSBeep(); fclose(f); } return YES; } if (l->type==LINK_DOWNLOAD) { [mdb msg_needData: mid]; return YES; } NSLog(@"unknown link type %i\n",l->type); return YES; } -(void) messageUpdate: (MidNotification *)n { if ([n mid]!=mid) return; [self getData]; } - initWithMsgDB: (MsgDB *)m textView: (NSTextView *)textview scrollView: (NSScrollView *)scrollview; { if (!(self=[super init])) return nil; if (!header_bold_dict) { /* TODO */ NSColor *fore_color=[NSColor colorWithCalibratedRed:0.1 green:0.1 blue:0.6 alpha: 1.0]; // NSColor *back_color=[NSColor colorWithCalibratedRed:0.7 green:0.7 blue:0.7 alpha: 1.0]; header_bold_dict=[[NSDictionary dictionaryWithObjectsAndKeys: // back_color,NSBackgroundColorAttributeName, fore_color,NSForegroundColorAttributeName, [NSFont boldSystemFontOfSize: 0],NSFontAttributeName, nil] retain]; header_dict=[[NSDictionary dictionaryWithObjectsAndKeys: // back_color,NSBackgroundColorAttributeName, fore_color,NSForegroundColorAttributeName, [NSFont userFontOfSize: 0],NSFontAttributeName, nil] retain]; header_link_dict=[[NSDictionary dictionaryWithObjectsAndKeys: // back_color,NSBackgroundColorAttributeName, [NSColor redColor],NSForegroundColorAttributeName, [NSFont userFontOfSize: 0],NSFontAttributeName, nil] retain]; } #ifdef USE_MIME_STUFF if (!mime_manager) { mime_manager=[[MimeManager alloc] init]; } #endif ASSIGN(mdb,m); ASSIGN(tv,textview); ASSIGN(sv,scrollview); [tv setDelegate: self]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(messageUpdate:) name: MsgDB_MsgDStatusNotification object: mdb]; mid=0; return self; } -(void) dealloc { if (cur_options) { NSFreeMapTable(cur_options); cur_options=NULL; } DESTROY(cur_message); [[NSNotificationCenter defaultCenter] removeObserver: self]; [tv setDelegate: nil]; DESTROY(tv); DESTROY(sv); DESTROY(mdb); [super dealloc]; } -(void) setMid: (msg_id_t)m { if (mid==m) return; if (mid) [mdb msg_cancelWantData: mid]; mid=m; [self getData]; } -(BOOL) atEnd { NSRect r1=[[sv contentView] documentRect]; NSRect r2=[[sv contentView] documentVisibleRect]; NSRect r3=[[sv contentView] frame]; if (r2.origin.y+r3.size.height16) y-=16; idx=[[tv layoutManager] glyphIndexForPoint: NSMakePoint(0,y) inTextContainer: [tv textContainer]]; i2=idx; /* First skip backwards and check if the current line is quoted */ for (;idx>=0;idx--) if ([s characterAtIndex: idx]=='\n') break; idx++; /* Find the first unquoted line */ for (;idx<[s length];) { for (;idx<[s length];idx++) { ch=[s characterAtIndex: idx]; if (ch=='>' || ch>32) break; } if (idx>=[s length]) break; if ([s characterAtIndex: idx]=='>') { for (idx++;idx<[s length];idx++) if ([s characterAtIndex: idx]=='\n') break; idx++; } else break; } /* nothing more interesting in this message? */ if (idx>=[s length]) return YES; /* if we're already past the signature, return YES */ if (sig_index!=NSNotFound && idx>sig_index) return YES; /* if we're on a really long, unquoted line we want to continue from the point in the line we were at before */ if (idx */ #ifndef MsgDB_h #define MsgDB_h typedef unsigned int msg_id_t; @class NSMutableDictionary,NSNotificationCenter,NSString; #define DSTATUS_NODATA 0 #define DSTATUS_DATA 1 #define DSTATUS_PENDING 2 #define DSTATUS_ERROR 3 #define DSTATUS_NOSOURCE 4 @class MsgDB; @protocol Msg_Source - initWithMsg: (msg_id_t)mid db: (MsgDB *)mdb; -(msg_id_t) mid; -(void) update; -(void) disconnect; -(unsigned int) getMessage: (msg_id_t)mid priority: (int)pri; -(BOOL) cancelGetMessage: (msg_id_t)mid id: (unsigned int) d; @end @protocol Msg_Folder -(int) numMessages; -(msg_id_t *) getMessages; @end @class NSString; @class NSMutableArray; @interface MsgDB : NSObject { msg_id_t main_id; NSString *dir; NSMutableDictionary *sources; msg_id_t last_source_id; struct message_s *messages; unsigned int num_messages; char **meta_headers; int num_meta_headers; struct msgdb_hash_s *hash; NSMutableDictionary *folders; NSNotificationCenter *ncenter; } - initWithDirectory: (NSString *)adir; -(NSNotificationCenter *)notificationCenter; -(void)setNotificationCenter: (NSNotificationCenter *)nc; -(msg_id_t) midForId: (const char *)msgid; -(msg_id_t) createMessageWithId: (const char *)msgid source: (NSObject *)src; -(void) setMessageData: (unsigned char *)d length: (int)l : (msg_id_t) mid; -(void) msg_setSource: (NSObject *)src : (msg_id_t) mid; -(void) updateAll; -(const char *) msg_getMessageID: (msg_id_t)mid; -(const char *)msg_getMetaHeader: (const char *)hname : (msg_id_t)mid; -(void)msg_setMetaHeader: (const char *)hname value: (const char *)value : (msg_id_t)mid; -(const char *)msg_getMetaHeaderWithNumber: (int)num : (msg_id_t)mid; -(void)msg_setMetaHeaderWithNumber: (int)num value: (const char *)value : (msg_id_t)mid; -(const char *)msg_getHeader: (const char *)hname : (msg_id_t)mid; /* priority ~10 for something the user wants to view, ~0 for read-ahead, ~-10 for bulk download */ -(void) msg_wantData: (msg_id_t)mid priority: (int)pri; -(void) msg_needData: (msg_id_t)mid; -(void) msg_cancelWantData: (msg_id_t)mid; -(int) msg_getData: (const unsigned char **)data length: (int *)length : (msg_id_t)mid; -(int) msg_dstatus: (msg_id_t)mid; -(void)msg_addToFolder: (const char *)foldername : (msg_id_t)mid; -(int) getMetaHeaderNum: (const char *)hname; -(const char *) getMetaHeaderName: (int)num; -(void) syncToDisk; -(void)dumpMessages; -(void)dumpMessage: (msg_id_t)mid; -(NSDictionary *) folders; -(NSArray *) sources; -(NSObject *) addSourceOfType: (Class)c; -(void) removeSource: (NSObject *)src; @end extern NSString *MsgDB_MsgDStatusNotification, *MsgDB_MsgMetaChangeNotification, *MsgDB_LogMessageNotification, *MsgDB_FolderAddMsgNotification, *MsgDB_FolderAddNotification, *MsgDB_SourceAddNotification, *MsgDB_SourceRemoveNotification, *MsgDB_SourceChangeNotification; #include @interface MidNotification : NSNotification { NSString *name; id object; msg_id_t mid; NSObject *folder; int header; } + notificationWithName: (NSString *)name object: (id)o mid: (msg_id_t)m folder: (NSObject *)f header: (int)h; - initWithName: (NSString *)n object: (id)o mid: (msg_id_t)m folder: (NSObject *)f header: (int)h; -(msg_id_t) mid; -(NSObject *) folder; -(const char *) headerName; @end #endif LuserNET-0.4.2/MsgDB.m100664 764 764 53523 10021217655 12476 0ustar alexalex/* copyright 2002 Alexander Malmberg */ /* TODO: cleanups, better organization */ #include #include #include #include #include #include #include #include #include #include "MsgDB.h" #define HASH_SIZE (1031*5) NSString *MsgDB_MsgDStatusNotification=@"MsgDB_MsgDStatusNotification", *MsgDB_MsgMetaChangeNotification=@"MsgDB_MsgMetaChangeNotification", *MsgDB_LogMessageNotification=@"MsgDB_LogMessageNotification", *MsgDB_FolderAddMsgNotification=@"MsgDB_FolderAddMsgNotification", *MsgDB_FolderAddNotification=@"MsgDB_FolderAddNotification", *MsgDB_SourceAddNotification=@"MsgDB_SourceAddNotification", *MsgDB_SourceRemoveNotification=@"MsgDB_SourceRemoveNotification", *MsgDB_SourceChangeNotification=@"MsgDB_SourceChangeNotification"; @implementation MidNotification + notificationWithName: (NSString *)n object: (id)o mid: (msg_id_t)m folder: (NSObject *)f header: (int)h { MidNotification *mn; mn=[[self alloc] initWithName: n object: o mid: m folder: f header: h]; return [mn autorelease]; } - initWithName: (NSString *)n object: (id)o mid: (msg_id_t)m folder: (NSObject *)f header: (int)h { if (!(self=[super init])) return nil; name=[n copy]; object=[o retain]; mid=m; folder=f; header=h; return self; } -(void) dealloc { DESTROY(name); DESTROY(object); [super dealloc]; } -(NSString *) name { return name; } -(id) object { return object; } -(NSDictionary *) userInfo { return nil; } -(msg_id_t) mid { return mid; } -(NSObject *) folder { return folder; } -(const char *) headerName { return [[self object] getMetaHeaderName: header]; } @end @interface NSDictionary (MsgDB_extensions) -(id) keyForObject: (id)o; @end @implementation NSDictionary (MsgDB_extensions) -(id) keyForObject: (id)o { NSEnumerator *e; id k; e=[self keyEnumerator]; for (;(k=[e nextObject]);) if (o==[self objectForKey: k]) return k; return nil; } @end typedef struct msgdb_hash_s { int size; msg_id_t *h; } hash_t; typedef struct { int mhid; char *value; } meta_header_t; typedef struct message_s { char *message_id; meta_header_t *mhs; int num_mhs; msg_id_t hash_next; int dstatus; char *data; int data_length; unsigned int src_get_id; } message_t; @interface MsgDB_Folder : NSObject { msg_id_t *msg; int num_msg; } -(BOOL) containsMessage: (msg_id_t)mid; -(void) addMessage: (msg_id_t)mid; -(void) removeMessage: (msg_id_t)mid; @end @implementation MsgDB_Folder - init { if (!(self=[super init])) return nil; return self; } -(void) dealloc { if (msg) { free(msg); } msg=NULL; num_msg=0; [super dealloc]; } -(BOOL) containsMessage: (msg_id_t)mid { /* should do binary search here */ int l,h,m; l=0; h=num_msg-1; while (l<=h) { m=(l+h)/2; if (msg[m]==mid) return YES; if (mid=0;i--) { if (msg[i]==mid) return; if (msg[i]i;j--) msg[j]=msg[j-1]; msg[i]=mid; num_msg++; /* int i; for (i=0;imessage_id); hv%=hash->size; m->hash_next=hash->h[hv]; hash->h[hv]=mid; } -(msg_id_t) findHash: (const char *)message_id { unsigned int hv=hash_id(message_id); msg_id_t mid; hv%=hash->size; for (mid=hash->h[hv];mid;mid=messages[mid].hash_next) if (!strcmp(message_id,messages[mid].message_id)) return mid; return 0; } @end #include @implementation MsgDB - initWithDirectory: (NSString *)adir { self=[super init]; if (!self) return nil; dir=[adir copy]; // fprintf(stderr,"init %p in %@\n",self,dir); mkdir([dir cString],0777); /* TODO */ num_messages=1; hash=malloc(sizeof(hash_t)); if (!hash) return nil; hash->size=HASH_SIZE; hash->h=malloc(sizeof(msg_id_t)*hash->size); memset(hash->h,0,sizeof(msg_id_t)*hash->size); ncenter=[NSNotificationCenter defaultCenter]; sources=[[NSMutableDictionary alloc] init]; folders=[[NSMutableDictionary alloc] init]; { int i; char buf[128]; Class c; const char *next; msg_id_t mid; NSNumber *n; NSObject *src; if (![self loadFromDisk]) [self createFromScratch]; mid=main_id; while (1) { next=[self msg_getMetaHeader: "Next-source" : mid]; if (!next) break; i=atoi(next); if (i==-1) break; snprintf(buf,sizeof(buf),"source-%i",i); mid=[self midForId: buf]; if (!mid) { fprintf(stderr,"Warning: Broken source chain!\n"); break; } // fprintf(stderr,"got '%s' type '%s'\n",buf,[self msg_getMetaHeader: "Type" : mid]); c=objc_lookup_class([self msg_getMetaHeader: "Type" : mid]); n=[[NSNumber alloc] initWithInt: i]; src=[[c alloc] initWithMsg: mid db: self]; [sources setObject: src forKey: n]; [src release]; [n release]; } last_source_id=mid; } return self; } -(void) dealloc { /* TODO: fix all the leaks */ // fprintf(stderr,"MsgDB: deallocing %p\n",self); { CREATE_AUTORELEASE_POOL(arp); NSEnumerator *e; NSObject *src; for (e=[sources objectEnumerator];(src=[e nextObject]);) [src disconnect]; #ifdef DEBUG_AUTORELEASE_COUNT fprintf(stderr,"dealloc arp contains %i objects\n",[arp autoreleaseCount]); #endif DESTROY(arp); } DESTROY(sources); DESTROY(dir); DESTROY(folders); { int i,j; message_t *m; for (i=0;imessage_id); for (j=0;jnum_mhs;j++) free(m->mhs[j].value); if (m->data) free(m->data); } } free(messages); [super dealloc]; } -(NSNotificationCenter *)notificationCenter { return ncenter; } -(void)setNotificationCenter: (NSNotificationCenter *)nc { ncenter=nc; } -(msg_id_t) midForId: (const char *)msgid { int i; message_t *m; if (hash) return [self findHash: msgid]; for (i=1,m=messages+1;imessage_id)) return i; } return 0; } -(msg_id_t) createMessageWithId: (const char *)msgid source: (NSObject *)src { message_t *m; if ([self midForId: msgid]) return 0; m=realloc(messages,sizeof(message_t)*(num_messages+1)); if (!m) abort(); messages=m; m=messages+num_messages; memset(m,0,sizeof(message_t)); m->message_id=strdup(msgid); if (!m->message_id) abort(); num_messages++; if (src) { int num=[[sources keyForObject: src] intValue]; char buf[16]; snprintf(buf,sizeof(buf),"%i",num); [self msg_setMetaHeader: "Source" value: buf : num_messages-1]; } [self addToHash: num_messages-1]; return num_messages-1; } -(void) msg_setSource: (NSObject *)src : (msg_id_t) mid { if (src) { int num=[[sources keyForObject: src] intValue]; char buf[16]; snprintf(buf,sizeof(buf),"%i",num); [self msg_setMetaHeader: "Source" value: buf : mid]; } } -(int) getMetaHeaderNum: (const char *)hname { int i; char **c; for (i=0;i=num_meta_headers) return NULL; return meta_headers[num]; } -(void) updateAll { CREATE_AUTORELEASE_POOL(arp); NSEnumerator *e; NSObject *src; for (e=[sources objectEnumerator];(src=[e nextObject]);) [src update]; #ifdef DEBUG_AUTORELEASE_COUNT fprintf(stderr,"updateAll arp contains %i objects\n",[arp autoreleaseCount]); #endif DESTROY(arp); } -(const char *)msg_getHeader: (const char *)hname : (msg_id_t)mid { char *buf; const char *c; buf=malloc(strlen(hname)+6); if (!buf) abort(); strcpy(buf,"Real-"); strcat(buf,hname); c=[self msg_getMetaHeader: buf : mid]; free(buf); if (c) return c; return NULL; /* TODO */ } -(const char *) msg_getMessageID: (msg_id_t)mid { message_t *m=messages+mid; return m->message_id; } -(const char *)msg_getMetaHeaderWithNumber: (int)hn : (msg_id_t)mid { int i; message_t *m=messages+mid; for (i=0;inum_mhs;i++) if (m->mhs[i].mhid==hn) return m->mhs[i].value; return NULL; } -(void)msg_setMetaHeaderWithNumber: (int) hn value: (const char *)value : (msg_id_t)mid { int i; char *c; message_t *m=messages+mid; meta_header_t *mh; if (!value) abort(); c=strdup(value); if (!c) abort(); for (i=0;inum_mhs;i++) if (m->mhs[i].mhid==hn) { free(m->mhs[i].value); m->mhs[i].value=c; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_MsgMetaChangeNotification object: self mid: mid folder: nil header: hn]]; return; } mh=realloc(m->mhs,sizeof(meta_header_t)*(m->num_mhs+1)); if (!mh) abort(); m->mhs=mh; mh+=m->num_mhs++; mh->mhid=hn; mh->value=c; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_MsgMetaChangeNotification object: self mid: mid folder: nil header: hn]]; } -(const char *)msg_getMetaHeader: (const char *)hname : (msg_id_t)mid { return [self msg_getMetaHeaderWithNumber: [self getMetaHeaderNum: hname] : mid]; } -(void)msg_setMetaHeader: (const char *)hname value: (const char *)value : (msg_id_t)mid { [self msg_setMetaHeaderWithNumber: [self getMetaHeaderNum: hname] value: value : mid]; } -(void)msg_addToFolder: (const char *)foldername : (msg_id_t)mid { const char *c; char *buf; MsgDB_Folder *f=[self createFolderWithName: [NSString stringWithCString: foldername]]; if ([f containsMessage: mid]) return; c=[self msg_getMetaHeader: "Folder" : mid]; if (c) { buf=malloc(strlen(c)+1+strlen(foldername)+1); if (!buf) abort(); sprintf(buf,"%s %s",c,foldername); [self msg_setMetaHeader: "Folder" value: buf : mid]; free(buf); } else [self msg_setMetaHeader: "Folder" value: foldername : mid]; [f addMessage: mid]; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_FolderAddMsgNotification object: self mid: mid folder: f header: -1]]; } -(void)dumpMessages { int i/*,n*/; /* msg_id_t mid;*/ for (i=1;isize,num_messages); for (i=0;isize;i++) { fprintf(stderr,"%03x:",i); n=0; for (mid=hash->h[i];mid;mid=messages[mid].hash_next) // fprintf(stderr," %i",mid); n++; fprintf(stderr," %i\n",n); } #endif fprintf(stderr,"..\n"); } -(void)dumpMessage: (msg_id_t)mid { message_t *m; int i; printf("-- message %i\n",mid); if (mid<0 || mid>=num_messages) { printf("no such message\n"); return; } m=messages+mid; printf("Message-Id: %s\n",m->message_id); for (i=0;inum_mhs;i++) printf("%2i : %3i '%s' '%s'\n",i,m->mhs[i].mhid,[self getMetaHeaderName: m->mhs[i].mhid],m->mhs[i].value); } -(void) syncToDisk { CREATE_AUTORELEASE_POOL(arp); const char *fname= [[[dir stringByAppendingPathComponent: @"metahead.txt"] stringByStandardizingPath] fileSystemRepresentation]; FILE *f; int i,j; message_t *m; // fprintf(stderr,"saving to '%s'\n",fname); f=fopen(fname,"wt"); if (!f) { fprintf(stderr,"couldn't save to '%s': %m\n",fname); return; } fprintf(f,"%i\n",num_meta_headers); for (i=0;inum_mhs,strlen(m->message_id)); fwrite(m->message_id,1,strlen(m->message_id),f); fputc('\n',f); for (j=0;jnum_mhs;j++) { fprintf(f,"%i %i ",m->mhs[j].mhid,strlen(m->mhs[j].value)); fwrite(m->mhs[j].value,1,strlen(m->mhs[j].value),f); fputc('\n',f); } } fclose(f); #ifdef DEBUG_AUTORELEASE_COUNT fprintf(stderr,"syncToDisk arp contains %i objects\n",[arp autoreleaseCount]); #endif DESTROY(arp); } -(int) loadFromDisk { CREATE_AUTORELEASE_POOL(arp); const char *fname= [[[dir stringByAppendingPathComponent: @"metahead.txt"] stringByStandardizingPath] fileSystemRepresentation]; FILE *f; int i,j,k,l; message_t *m; int *mhidx; char **mhname; int nmh; f=fopen(fname,"rt"); if (!f) return 0; if (fscanf(f,"%i\n",&nmh)!=1) { fprintf(stderr,"parse error\n"); return 0; } mhidx=malloc(nmh*sizeof(int)); mhname=malloc(nmh*sizeof(char *)); if (!mhidx || !mhname) abort(); for (i=0;inum_mhs,&l); m->message_id=malloc(l+1); if (!m->message_id) abort(); fread(m->message_id,1,l,f); m->message_id[l]=0; fgetc(f); m->mhs=malloc(sizeof(meta_header_t)*m->num_mhs); if (!m->mhs) abort(); for (j=0;jnum_mhs;j++) { fscanf(f,"%i %i",&k,&l); fgetc(f); if (mhidx[k]==-1) mhidx[k]=[self getMetaHeaderNum: mhname[k]]; m->mhs[j].mhid=mhidx[k]; m->mhs[j].value=malloc(l+1); if (!m->mhs[j].value) abort(); fread(m->mhs[j].value,1,l,f); m->mhs[j].value[l]=0; fgetc(f); } } { char **folder_names=NULL; MsgDB_Folder **f=NULL; int nfn=0; char *c,*d; int j; int idx=[self getMetaHeaderNum: "Folder"]; for (i=1,m=messages+1;inum_mhs;j++) if (m->mhs[j].mhid==idx) break; if (j==m->num_mhs) continue; for (c=m->mhs[j].value;*c;) { for (d=c;*d && *d!=' ';d++) ; for (j=0;jdstatus==dstatus) return; m->dstatus=dstatus; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_MsgDStatusNotification object: self mid: mid folder: nil header: -1]]; } -(id) msg_source: (msg_id_t)mid { NSNumber *n; id src; const char *c; c=[self msg_getMetaHeader: "Source" : mid]; if (!c) return nil; n=[[NSNumber alloc] initWithInt: atoi(c)]; src=[sources objectForKey: n]; [n release]; if (!src) return nil; return src; } -(void)msg_wantData: (msg_id_t)mid priority: (int)pri { id src; message_t *m=&messages[mid]; if (m->dstatus==DSTATUS_DATA) return; if (m->dstatus==DSTATUS_PENDING) return; src=[self msg_source: mid]; if (!src) { [self msgp_setDStatus: DSTATUS_NOSOURCE : mid]; return; } [self msgp_setDStatus: DSTATUS_PENDING : mid]; m->src_get_id=[src getMessage: mid priority: pri]; } -(void) msg_cancelWantData: (msg_id_t)mid { id src; message_t *m=&messages[mid]; if (!m->src_get_id) return; src=[self msg_source: mid]; if ([src cancelGetMessage: mid id: m->src_get_id]) { m->src_get_id=0; m->dstatus=DSTATUS_NODATA; } } -(void)msg_needData: (msg_id_t)mid { [self msg_wantData: mid priority: 10]; } -(int)msg_getData: (const unsigned char **)data length: (int *)length : (msg_id_t)mid { message_t *m=&messages[mid]; if (m->dstatus==DSTATUS_DATA) { *data=m->data; *length=m->data_length; return 1; } if (m->dstatus==DSTATUS_NODATA) { // [self msg_needData: mid]; /* TODO? */ *data=NULL; *length=0; } return m->dstatus; } -(int)msg_dstatus: (msg_id_t)mid { return messages[mid].dstatus; } -(void) setMessageData: (unsigned char *)d length: (int)l : (msg_id_t) mid { message_t *m=&messages[mid]; m->src_get_id=0; if (l==-1) { /* if we already have data we keep it and ignore the error */ if (m->dstatus==DSTATUS_DATA) return; [self msgp_setDStatus: DSTATUS_ERROR : mid]; return; } if (m->dstatus==DSTATUS_DATA) { free(m->data); m->data=NULL; m->data_length=0; m->dstatus=DSTATUS_NODATA; } m->data=d; m->data_length=l; [self msgp_setDStatus: DSTATUS_DATA : mid]; /* If we don't already have headers for this message we parse them from the data. We're only interested in some headers, though, and we want the raw header data, so we parse manually. It won't be less accurate than xover headers. */ /* TODO: re-parse anyway? */ // if (![self msg_getMetaHeader: "Real-Message-ID" : mid]) { unsigned char *b,*end; const char *c; NSMutableData *header=[[NSMutableData alloc] initWithCapacity: 100]; NSMutableData *value=[[NSMutableData alloc] initWithCapacity: 100]; b=m->data; end=b+m->data_length; while (b=end) break; while (1) { if (*b=='\n' && b+132 || b[1]=='\n')) break; if (*b=='\n' || *b=='\r') { b++; continue; } [value appendBytes: b length: 1]; b++; } b++; [header appendBytes: "\x0" length: 1]; [value appendBytes: "\x0" length: 1]; c=[header bytes]; if (!strcmp(c,"Real-Subject") || !strcmp(c,"Real-Message-ID") || !strcmp(c,"Real-From") || !strcmp(c,"Real-Date") || !strcmp(c,"Real-References") || !strcmp(c,"Real-Bytes") || !strcmp(c,"Real-Lines") ) { [self msg_setMetaHeader: c value: [value bytes] : mid]; } } DESTROY(header); DESTROY(value); } } -(void) createFromScratch { main_id=[self createMessageWithId: "main" source: nil]; } -(NSArray *) sources { return [sources allValues]; } -(NSObject *) addSourceOfType: (Class)c { msg_id_t mid; int idx; const char *b; char buf[64]; NSObject *src; NSNumber *n; b=[self msg_getMetaHeader: "Max-source" : main_id]; if (!b) idx=1; else idx=atoi(b)+1; sprintf(buf,"%i",idx); [self msg_setMetaHeader: "Max-source" value: buf : main_id]; [self msg_setMetaHeader: "Next-source" value: buf : last_source_id]; sprintf(buf,"source-%i",idx); last_source_id=mid=[self createMessageWithId: buf source: nil]; if (!mid) { /* TODO */ abort(); } [self msg_setMetaHeader: "Type" value: [NSStringFromClass(c) cString] : mid]; n=[[NSNumber alloc] initWithInt: idx]; src=[[c alloc] initWithMsg: mid db: self]; [sources setObject: src forKey: n]; [src autorelease]; [n release]; [ncenter postNotificationName: MsgDB_SourceAddNotification object: self]; return src; } -(void) removeSource: (NSObject *)src { msg_id_t mid,next_mid,src_mid; const char *next; int i; char buf[64]; [src retain]; [sources removeObjectForKey: [sources keyForObject: src]]; src_mid=[src mid]; mid=main_id; while (1) { next=[self msg_getMetaHeader: "Next-source" : mid]; if (!next) break; i=atoi(next); if (i==-1) break; snprintf(buf,sizeof(buf),"source-%i",i); next_mid=[self midForId: buf]; if (!next_mid) { fprintf(stderr,"Warning: Broken source chain!\n"); break; } if (next_mid==src_mid) { if ([self msg_getMetaHeader: "Next-source" : src_mid]) [self msg_setMetaHeader: "Next-source" value: [self msg_getMetaHeader: "Next-source" : src_mid] : mid]; else [self msg_setMetaHeader: "Next-source" value: "-1" : mid]; if (last_source_id==src_mid) last_source_id=mid; break; } mid=next_mid; } /* TODO: delete actual message */ [src release]; [ncenter postNotificationName: MsgDB_SourceRemoveNotification object: self]; } @end LuserNET-0.4.2/NNTPServer.h100664 764 764 10054 10021217655 13473 0ustar alexalex/* copyright 2001-2002 Alexander Malmberg */ #ifndef NNTPServer_h #define NNTPServer_h #include @class NSObject; @class NSRunLoop; @class NSString; @protocol NNTPServer_Receiver -(void) nntp_serverDate: (const char *)date qid: (unsigned int)qid; /* if num=-1 the group didn't exist */ -(void) nntp_groupInfo: (int)group : (int)num : (int)first : (int)last qid: (unsigned int)qid; /* order is: 'xref-number Subject Author Date Message-ID References bytes lines' and maybe other optional headers '(header-name: data)*' tabs between headers This might be called several times for a single qid if the request is large. Every call except the last call will have partial==YES */ /* TODO: parse and return in nicer form? */ -(void) nntp_headerRange: (int)group : (int)first : (int)last : (int)num : (char **)list partial: (BOOL)p qid: (unsigned int)qid; -(void) nntp_headerId: (const char *)mid data: (const unsigned char *)data : (int)length qid: (unsigned int)qid; -(void) nntp_articleRange: (int)group : (int)num data: (const unsigned char *)data : (int)length qid: (unsigned int)qid; -(void) nntp_articleId: (const char *)mid data: (const unsigned char *)data : (int)length qid: (unsigned int)qid; /* name last first may-post */ -(void) nntp_groupList: (const unsigned char *)data : (int)length qid: (unsigned int)qid; /* NO if the article was not accepted by the server (malformed message, cancelled request, etc.). */ -(void) nntp_postArticle: (BOOL)success qid: (unsigned int)qid; -(void) nntp_fail: (NSString *)reason qid: (unsigned int)qid; /* Called if we can't open any connection at all to the server. should_connect will be set to 0 before this call, so you'll have to explictly set it to 1 if you want to try connecting again. */ -(void) nntp_fail_connect: (NSString *)reason; -(void) nntp_message: (NSString *)msg; -(void) nntp_progress: (int)bytes : (unsigned int)qid; @end @interface NNTPServer : NSObject { struct nntp_connection_s *cons; int num_cons; int num_idle,num_active,num_starting; struct nntp_queue_s *queue; int should_connect; char *host; int have_addr; struct sockaddr_in addr; int port; int protocol; NSRunLoop *runloop; NSArray *runmodes; id rec; NSString *last_error; NSTimeInterval last_connect_attempt,lca_delta; int set_timeout; } +(int) getGroupNum: (const char *)group; +(const char *) getGroupName: (int)group; -init; -initWithHost: (const char *)host; -initWithHost: (const char *)host port: (int)port; -initWithAddr: (struct sockaddr_in *)addr port: (int)port host: (const char *)host; -(void) enableConnect: (int)should_connect; -(void) enableTimeout: (int)should_connect; -(void) setReceiver: (id)arec; -(void) closeAllConnections; -(void) killAllConnections; -(NSString *)qidDescription: (unsigned int)qid; /* -10000 < priority < 10000 default is 0, use lower for background bulk transfers/read-ahead, higher for 'interactive' stuff (like the article the user just clicked on) */ -(unsigned int) getServerDate; -(unsigned int) getGroupList: (int)priority; //-(unsigned int) getGroupListSince: (const char *)date; -(unsigned int) getGroupInfo: (int)group; -(unsigned int) getHeaderRange: (int)low : (int)high group: (int)group priority: (int)pri; -(unsigned int) getHeaderById: (const char *)msg_id priority: (int)pri; -(unsigned int) getArticleRange: (int)low : (int)high group: (int)group priority: (int)pri; -(unsigned int) getArticleById: (const char *)msg_id priority: (int)priority; -(unsigned int) getArticleById: (const char *)msg_id size: (int)bytes priority: (int)pri; /* a copy is made of the data, so the caller does not have to keep it around */ -(unsigned int) postArticle: (unsigned char *)data length: (int)length priority: (int)priority; /* If kill is YES, try _really_ hard to cancel (ie. if the request is already in progress on a connection, kill the connection; this is not nice on the server, so use only when really necessary). */ -(BOOL) cancelQid: (unsigned int)qid kill: (BOOL)kill; @end #endif LuserNET-0.4.2/NNTPServer.m100664 764 764 124231 10021217655 13523 0ustar alexalex/* copyright 2001-2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "NNTPServer.h" /* TODO: owner specifies modes? */ #include //#define DEBUG_SCHEDULER #ifdef DEBUG_SCHEDULER @interface NNTPServer (debug_scheduler) -(void) _updateDebugScheduler; @end #endif /* TODO: better error handling? */ static NSTimeInterval Now(void) { return [NSDate timeIntervalSinceReferenceDate]; } //#define DEBUG_SOCKET /* based on a sample of 29314 messages from a few different (non-binary) groups */ #define ESTIMATED_MSG_SIZE 2183 /* TODO: for temporary xover stat collection */ //#define XOVER_STATS #ifdef XOVER_STATS static int stat_num_headers,stat_num_expected_headers,stat_header_bytes; #endif #define MAX_CONNECTIONS 2 typedef struct { int time_limit; /* open if work is above work_limit, close after */ int work_limit; /* workqe_size=8; q->qid=1; q->qe=malloc(sizeof(queue_entry_t)*q->qe_size); if (!q->qe) return NULL; q->qe[0].priority=10000; return q; } static void queue_free(queue_t *q) { free(q->qe); free(q); } /* return an estimated number of bytes returned */ static int queue_work(queue_entry_t *qe) { switch (qe->what) { case WHAT_NONE: return 0; case WHAT_DATE: return 10; case WHAT_GROUP_INFO: return 10; case WHAT_HEADER_ID: return 500; case WHAT_HEADER_RANGE: /* testing suggets 380 bytes/message */ return (qe->d.range.h-qe->d.range.l+1)*380; case WHAT_ARTICLE_ID: return qe->d.msg.bytes; case WHAT_ARTICLE_RANGE: return (qe->d.range.h-qe->d.range.l+1)*ESTIMATED_MSG_SIZE; case WHAT_GROUP_LIST: return 10000; /* will probably be a lot more, but could be a lot less */ case WHAT_POST_ARTICLE: return qe->d.post.length; } fprintf(stderr,"unhandled q->what %i in queue_work()\n",qe->what); abort(); return 1; /* shouldn't happen */ } static unsigned int queue_add(queue_t *q,signed short priority,queue_entry_t *qadd) { queue_entry_t *qe; int i; if (q->qe_num==q->qe_size-1) { queue_entry_t *e; int nsize; if (q->qe_size>1024) nsize=q->qe_size+1024; else nsize=q->qe_size*2; e=realloc(q->qe,nsize*sizeof(queue_entry_t)); if (!e) { /* TODO: very difficult to handle gracefully, but should never happen */ fprintf(stderr,"queue_add: out of memory, dropping\n"); abort(); // return NULL; } q->qe=e; q->qe_size=nsize; } q->qe_num++; for (i=q->qe_num;q->qe[i/2].priorityqe[i]=q->qe[i/2]; qe=&q->qe[i]; *qe=*qadd; qe->priority=priority; qe->qid=q->qid++; q->total_work+=queue_work(qe); return qe->qid; } #define QE_LESS(q1,q2) ( ((q2)->priority>(q1)->priority) || ( ((q1)->priority==(q2)->priority) && ((q2)->qid<(q1)->qid) ) ) static void queue_remove(queue_t *q,queue_entry_t *qe) { queue_entry_t *ins; int pri; int i,j; pri=qe->priority; i=qe-q->qe; if (i<1 || i>q->qe_num) return; q->total_work-=queue_work(qe); ins=&q->qe[q->qe_num]; q->qe_num--; qe=ins; if (qe->priority==pri) { q->qe[i]=*qe; return; } if (qe->priorityqe_num;i=j) { j=i*2; if (j+1<=q->qe_num && QE_LESS(&q->qe[j],&q->qe[j+1])) j++; if (QE_LESS(qe,&q->qe[j])) q->qe[i]=q->qe[j]; else break; } q->qe[i]=*qe; } else { for (;q->qe[i/2].prioritypriority;i/=2) q->qe[i]=q->qe[i/2]; q->qe[i]=*qe; } } static queue_entry_t *queue_get(queue_t *q) { if (!q->qe_num) return NULL; return &q->qe[1]; } static void queue_delete_max(queue_t *q) { int i,j; queue_entry_t *qe; q->total_work-=queue_work(&q->qe[1]); qe=&q->qe[q->qe_num--]; for (i=1;i*2<=q->qe_num;i=j) { j=i*2; if (j+1<=q->qe_num && QE_LESS(&q->qe[j],&q->qe[j+1])) j++; if (QE_LESS(qe,&q->qe[j])) q->qe[i]=q->qe[j]; else break; } q->qe[i]=*qe; } #undef QE_LESS static void queue_entry_clear(queue_entry_t *q) { if (q->what==WHAT_HEADER_ID || q->what==WHAT_ARTICLE_ID ) if (q->d.msg.id) { free(q->d.msg.id); q->d.msg.id=NULL; } if (q->what==WHAT_POST_ARTICLE) if (q->d.post.data) { free(q->d.post.data); q->d.post.data=NULL; } q->what=WHAT_NONE; } typedef enum { S_NONE, S_IDLE, S_CONNECT1, /* connected, waiting for hello message */ /* send 'mode reader' */ S_CONNECT2, /* waiting for 'mode reader' response */ S_QUIT, /* waiting for 205 response */ S_DATE, /* waiting for DATE response */ S_GROUP, /* waiting for GROUP response for group info */ S_SWITCH_GROUP, /* waiting for GROUP response while switching groups */ /* for some other command */ S_HEADER_RANGE1, S_HEADER_RANGE2, S_HEADER_ID1, S_HEADER_ID2, S_ARTICLE_RANGE1, S_ARTICLE_RANGE2, S_ARTICLE_ID1, S_ARTICLE_ID2, S_GROUP_LIST1, S_GROUP_LIST2, S_POST_ARTICLE1, S_POST_ARTICLE2, } state_t; typedef struct nntp_connection_s { int valid; int sock; char *write_pending; int write_pending_ofs; int write_len; /* 0=no response yet 1=have status 2=have status, waiting for data 3=have status and data */ int resp; int resp_code; /* numeric code in status */ char *resp_msg; /* status message (if any) */ unsigned char *resp_buf; /* read buffer */ int resp_buf_len; /* length of buffer */ int resp_data_len; /* data has been processed up to this point */ int resp_data_nline; int resp_data_len_last; /* used to keep user updated on long responses */ state_t state; char should_close,reg_write; queue_entry_t cur; int cur_group; #ifdef DEBUG_SOCKET FILE *debug_write; #endif } con_t; static char **group_names; static int num_group_names; @interface NNTPServer (private) -(void) updateQueue; -setup; -(int) nc_connect: (con_t *)c; -(void) nc_update: (con_t *)c; -(void) nc_setState: (con_t *)c : (state_t)nstate; -(void) nc_close: (con_t *)c; -(void) nc_free: (con_t *)c; -(void) nc_parseResponse: (con_t *)c; -(void) nc_updateRead: (con_t *)c; -(void) nc_updateWrite: (con_t *)c; -(void) nc_writeCommand: (con_t *)c : (const char *)cmd, ...; -(void) nc_writeData: (con_t *)c : (unsigned char *)data : (int)length; -(void) nc_responseClear: (con_t *)c; -(void) nc_responseWantData: (con_t *)c; -(void) nc_updateState: (con_t *)c; -(void) nc_handleWhat: (con_t *)c; -(void) nc_fail: (con_t *)c; -(void) nc_timeout; @end @implementation NNTPServer (private) -setup { should_connect=0; runloop=[[NSRunLoop currentRunLoop] retain]; runmodes=[[NSArray alloc] initWithObjects: NSDefaultRunLoopMode, NSModalPanelRunLoopMode, NSEventTrackingRunLoopMode, nil]; queue=queue_new(); if (!queue) [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; num_cons=MAX_CONNECTIONS; cons=malloc(sizeof(con_t)*num_cons); if (!cons) [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; memset(cons,0,sizeof(con_t)*num_cons); lca_delta=-1.0; last_connect_attempt=Now(); // last_activity=Now(); return self; } -(int) fixAddr { if (!have_addr) { struct hostent *h; unsigned char *a; h=gethostbyname(host); if (!h) { DESTROY(last_error); last_error=[[NSString stringWithFormat: _(@"lookup of %s failed: %s"),host,strerror(errno)] retain]; return 0; } addr.sin_addr=*(struct in_addr *)h->h_addr; a=(unsigned char *)h->h_addr; [rec nntp_message: [NSString stringWithFormat: _(@"Resolved '%s' to %i.%i.%i.%i"), host,a[0],a[1],a[2],a[3]]]; have_addr=1; } if (port==-1) { struct servent *s; struct protoent *p; s=getservbyname("nntp","tcp"); if (!s) { [rec nntp_message: [NSString stringWithFormat: _(@"warning: can't find service 'nntp', assuming port 119")]]; port=119; p=getprotobyname("tcp"); } else { port=ntohs(s->s_port); p=getprotobyname(s->s_proto); } if (!p) { [rec nntp_message: [NSString stringWithFormat: _(@"warning: can't find protocol, assuming 0")]]; protocol=0; } else protocol=p->p_proto; } if (protocol==-1) { struct protoent *p; p=getprotobyname("tcp"); if (!p) { [rec nntp_message: [NSString stringWithFormat: _(@"warning: can't find protocol, assuming 0")]]; protocol=0; } else protocol=p->p_proto; } return 1; } -(int) nc_connect: (con_t *)c { if (![self fixAddr]) return 0; addr.sin_family=AF_INET; addr.sin_port=htons(port); #ifdef DEBUG_SOCKET { char buf[128]; sprintf(buf,"ncon%i.txt",c-cons); c->debug_write=fopen(buf,"at"); } #endif { unsigned char *a=(unsigned char *)&addr.sin_addr; [rec nntp_message: [NSString stringWithFormat: _(@"Connecting to %i.%i.%i.%i:%i..."), a[0],a[1],a[2],a[3],port]]; #ifdef DEBUG_SOCKET fprintf(c->debug_write,"-=- Connecting to %i.%i.%i.%i:%i\n",a[0],a[1],a[2],a[3],port);fflush(c->debug_write); #endif } c->sock=socket(PF_INET,SOCK_STREAM,protocol); if (c->sock<0) { DESTROY(last_error); last_error=[[NSString stringWithFormat: _(@"can't create socket: %s"),strerror(errno)] retain]; return 0; } if (connect(c->sock,(struct sockaddr *)&addr,sizeof(addr))<0) { DESTROY(last_error); last_error=[[NSString stringWithFormat: _(@"Can't open connection: %s"),strerror(errno)] retain]; close(c->sock); c->sock=-1; return 0; } { int flags; flags=fcntl(c->sock,F_GETFL); flags|=O_NONBLOCK; if (fcntl(c->sock,F_SETFL,flags)) { DESTROY(last_error); last_error=[[NSString stringWithFormat: _(@"fcntl failed to set non-blocking mode: %s"),strerror(errno)] retain]; close(c->sock); c->sock=-1; return 0; } } c->valid=1; { int i,count=[runmodes count]; for (i=0;isock type: ET_RDESC watcher: self forMode: [runmodes objectAtIndex: i]]; [runloop addEvent: (void *)c->sock type: ET_EDESC watcher: self forMode: [runmodes objectAtIndex: i]]; } } [self nc_setState: c : S_CONNECT1]; return 1; } -(void) nc_update: (con_t *)c { int i,count; if (c->state==S_NONE) return; count=[runmodes count]; if (c->reg_write && !c->write_pending && c->sock>=0) { for (i=0;isock type: ET_WDESC forMode: [runmodes objectAtIndex: i] all: YES]; c->reg_write=0; } else if (!c->reg_write && c->write_pending && c->sock>=0) { for (i=0;isock type: ET_WDESC watcher: self forMode: [runmodes objectAtIndex: i]]; c->reg_write=1; } } -(void) nc_setState: (con_t *)c : (state_t)nstate { if (c->state==nstate) return; if (c->state==S_CONNECT1 || c->state==S_CONNECT2) num_starting--; if (c->state==S_IDLE) num_idle--; if (c->state==S_NONE) num_active++; if (nstate==S_CONNECT1 || nstate==S_CONNECT2) num_starting++; if (nstate==S_IDLE) num_idle++; if (nstate==S_NONE) num_active--; c->state=nstate; } -(void) nc_close: (con_t *)c { if (c->state==S_NONE) return; c->should_close=1; if (set_timeout==1) { set_timeout=0; [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(nc_timeout) object: [NSObject class]]; } } -(void) nc_free: (con_t *)c { #ifdef DEBUG_SOCKET if (c->debug_write) { fclose(c->debug_write); c->debug_write=NULL; } #endif if (set_timeout==1) { set_timeout=0; [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(nc_timeout) object: [NSObject class]]; } queue_entry_clear(&c->cur); { int i,count=[runmodes count]; for (i=0;isock type: ET_RDESC forMode: [runmodes objectAtIndex: i] all: YES]; [runloop removeEvent: (void *)c->sock type: ET_WDESC forMode: [runmodes objectAtIndex: i] all: YES]; [runloop removeEvent: (void *)c->sock type: ET_EDESC forMode: [runmodes objectAtIndex: i] all: YES]; } } if (c->write_pending) { free(c->write_pending); c->write_pending=NULL; } if (c->resp_msg) { free(c->resp_msg); c->resp_msg=NULL; } if (c->resp_buf) { free(c->resp_buf); c->resp_buf=NULL; c->resp_buf_len=0; } if (c->sock>=0 && c->valid) { close(c->sock); } [self nc_setState: c : S_NONE]; memset(c,0,sizeof(con_t)); c->sock=-1; } -(void) nc_parseResponse: (con_t *)cn { if (!cn->resp_buf_len) return; if (cn->resp==0) { /* look for status line */ unsigned char *b,*c,*e; e=cn->resp_buf+cn->resp_buf_len; for (c=cn->resp_buf;cresp_buf+3; if (*b==' ') b++; if (c>b) { cn->resp_msg=malloc(c-b+1); if (!cn->resp_msg) [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; memcpy(cn->resp_msg,b,c-b); cn->resp_msg[c-b]=0; } cn->resp_code=atoi(cn->resp_buf); c+=2; if (c==e) cn->resp_buf_len=0; else { cn->resp_buf_len-=(c-cn->resp_buf); memmove(cn->resp_buf,c,cn->resp_buf_len); } /* status lines are probably short, so don't bother reallocating the buffer (we'd just end up allocating just as much on the next status line) */ cn->resp=1; } else if (cn->resp==2) { /* look for data ended by ".\r\n" alone on a line */ unsigned char *c,*d,*e; c=d=cn->resp_buf+cn->resp_data_len; e=cn->resp_buf+cn->resp_buf_len; for (;cresp_data_nline && *c=='.') { if (c>e-3) break; if (c[1]=='\r' && c[2]=='\n') { /* we have all the data */ cn->resp=3; break; } cn->resp_data_nline=0; continue; /* skip the first period */ } cn->resp_data_nline=0; if (cn->resp_data_len>0 && *c=='\n' && d[-1]=='\r') { d[-1]='\n'; cn->resp_data_nline=1; continue; } *d++=*c; cn->resp_data_len++; } if (c==e) { /* we ran out of data before finding the end resp_buf: | resp_data_len | */ cn->resp_buf_len=cn->resp_data_len; } else if (cn->resp==3) { /* we found the end, remove the end marker and fix the c/d gap resp_buf: | resp_data_len stuff(resp_buf_len-resp_data_len) | */ c+=3; if (c!=e) memmove(d,c,e-c); cn->resp_buf_len-=c-d; cn->resp_data_len_last=0; } else { /* we've found a period but can't tell if it's end of data yet (yuck) resp_buf: | resp_data_len stuff(resp_buf_len-resp_data_len) */ memmove(d,c,e-c); cn->resp_buf_len-=c-d; } } if (cn->state==S_HEADER_RANGE2) { if (cn->resp_data_len>48*1024) { char **list; int num; unsigned char *b,*d,*e; num=0; /* TODO: this will be excessively large almost all the time */ list=malloc(sizeof(char *)*(cn->cur.d.range.h-cn->cur.d.range.l+1)); d=b=cn->resp_buf; /* always leave at least one byte so there will be a final non-partial call to nntp_headerRange:... */ e=b+cn->resp_data_len-1; for (;bresp_buf; #endif [rec nntp_headerRange: cn->cur.d.range.group : cn->cur.d.range.l : cn->cur.d.range.h : num : list partial: YES qid: cn->cur.qid]; free(list); num=d-cn->resp_buf; memmove(cn->resp_buf,d,cn->resp_buf_len-num); cn->resp_buf_len-=num; cn->resp_data_len-=num; } } else { int i,j; i=cn->resp_data_len/(20*1024); j=cn->resp_data_len_last/(20*1024); if (i>j) { [rec nntp_progress: cn->resp_data_len : cn->cur.qid]; cn->resp_data_len_last=cn->resp_data_len; } } } -(void) nc_responseClear: (con_t *)c { if (c->resp==0 || c->resp==2) [NSException raise: @"NNTPServer_Error" format: @"responseClear called when resp=%i",c->resp]; if (c->resp==3) { unsigned char *b; c->resp_buf_len-=c->resp_data_len; /* data might have been large, so shrink the buffer again */ if (c->resp_buf_len) { memmove(c->resp_buf,c->resp_buf+c->resp_data_len,c->resp_buf_len); b=realloc(c->resp_buf,c->resp_buf_len); if (b) c->resp_buf=b; } else { free(c->resp_buf); c->resp_buf=NULL; } c->resp_data_len=0; } c->resp=0; if (c->resp_msg) free(c->resp_msg); c->resp_msg=NULL; [self nc_parseResponse: c]; } -(void) nc_responseWantData: (con_t *)c { if (c->resp!=1) [NSException raise: @"NNTPServer_Error" format: @"responseWantData called when resp=%i",c->resp]; c->resp=2; c->resp_data_len=0; c->resp_data_nline=0; [self nc_parseResponse: c]; } -(void) nc_updateRead: (con_t *)c { unsigned char buf[512]; int len,ilen; unsigned char *b; /* if the socket has already been closed we're just parsing data we've already recieved */ ilen=0; if (c->sock!=-1) { while ((len=read(c->sock,buf,sizeof(buf)))>0) { b=realloc(c->resp_buf,c->resp_buf_len+len); if (!b) { c->valid=0; /* TODO */ [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; } c->resp_buf=b; b+=c->resp_buf_len; memcpy(b,buf,len); c->resp_buf_len+=len; ilen+=len; #ifdef DEBUG_SOCKET { unsigned char b2[513]; memcpy(b2,buf,512); b2[len]=0; fprintf(c->debug_write,"%s",b2);fflush(c->debug_write); } #endif } if (len<0 && errno!=EAGAIN) { fprintf(stderr,"error reading from socket %i: %m\n",c->sock); c->valid=0; return; } if (len==0) { int i,count=[runmodes count]; close(c->sock); for (i=0;isock type: ET_RDESC forMode: [runmodes objectAtIndex: i] all: YES]; [runloop removeEvent: (void *)c->sock type: ET_EDESC forMode: [runmodes objectAtIndex: i] all: YES]; [runloop removeEvent: (void *)c->sock type: ET_WDESC forMode: [runmodes objectAtIndex: i] all: YES]; } c->sock=-1; } if (ilen!=0) [self nc_parseResponse: c]; } } -(void) nc_updateWrite: (con_t *)cn { const char *c; int len,wlen; if (!cn->write_pending) return; if (cn->sock==-1) { /* TODO */ free(cn->write_pending); cn->write_pending=NULL; cn->write_len=0; return; } c=cn->write_pending+cn->write_pending_ofs; len=cn->write_len-cn->write_pending_ofs; wlen=write(cn->sock,c,len); if (wlen<0) { if (errno==EAGAIN) return; fprintf(stderr,"error writing to socket %i: %m\n",cn->sock); cn->valid=0; return; } cn->write_pending_ofs+=wlen; if (wlen==len) { free(cn->write_pending); cn->write_pending=NULL; cn->write_len=0; } } -(void) nc_writeCommand: (con_t *)c : (const char *)cmd, ... { extern int vasprintf(char **c,const char *format,va_list args); /* TODO? */ va_list va; char *d; if (c->write_pending) { abort(); /* TODO? */ } if (!cmd) return; c->write_pending=NULL; va_start(va,cmd); vasprintf(&d,cmd,va); va_end(va); if (!d) [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; c->write_len=strlen(d)+2; d=realloc(d,c->write_len+1); if (!d) [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; c->write_pending=d; strcat(c->write_pending,"\r\n"); c->write_pending_ofs=0; #ifdef DEBUG_SOCKET fprintf(c->debug_write,"=C: %s",c->write_pending);fflush(c->debug_write); #endif [self nc_updateWrite: c]; } -(void) nc_writeData: (con_t *)c : (unsigned char *)data : (int)length { if (c->write_pending) { abort(); /* TODO? */ } c->write_pending=NULL; c->write_len=length; c->write_pending=data; c->write_pending_ofs=0; #ifdef DEBUG_SOCKET fprintf(c->debug_write,"=C DATA||\n"); fwrite(c->write_pending,1,c->write_len,c->debug_write); fprintf(c->debug_write,"||\n");fflush(c->debug_write); #endif [self nc_updateWrite: c]; } -(void) receivedEvent: (void *)data type: (RunLoopEventType) type extra: (void *)extra forMode: (NSString *)mode { CREATE_AUTORELEASE_POOL(arp); con_t *c; int i,j; // printf("--- got event %i type %i extra %p\n",(int)data,type,extra); j=(int)data; for (i=0,c=cons;isock==j) break; if (i==num_cons) { fprintf(stderr,"[NNTPServer receivedEvent] can't find connection for socket\n"); DESTROY(arp); return; } if (type==ET_EDESC) { /* probably eof */ [self nc_updateRead: c]; [self nc_updateState: c]; } else if (type==ET_WDESC) { [self nc_updateWrite: c]; } else if (type==ET_RDESC) { [self nc_updateRead: c]; [self nc_updateState: c]; } [self updateQueue]; [self nc_update: c]; // printf("--- done received\n"); #ifdef DEBUG_AUTORELEASE_COUNT fprintf(stderr,"receivedEvent arp contains %i objects\n",[arp autoreleaseCount]); #endif DESTROY(arp); } -(NSDate *) timedOutEvent: (void *)data type: (RunLoopEventType) type forMode: (NSString *)mode { /* shouldn't happen */ fprintf(stderr,"got timedOutEvent\n"); return nil; } -(void) nc_fail: (con_t *)c { [rec nntp_fail: [NSString stringWithFormat: @"Unexpected reply: %i %s",c->resp_code,c->resp_msg] qid: c->cur.qid]; } -(int) nc_switchGroup: (con_t *)c : (int)group { if (group==c->cur_group) return 0; [self nc_setState: c : S_SWITCH_GROUP]; [self nc_writeCommand: c : "GROUP %s",group_names[group]]; return 1; } -(void) nc_updateState: (con_t *)c { int code; queue_entry_t *q; while (c->resp==1 || c->resp==3) { code=c->resp_code; q=&c->cur; switch (c->state) { case S_NONE: [rec nntp_message: @"Warning: got response in state S_NONE"]; [self nc_responseClear: c]; break; case S_IDLE: fprintf(stderr,"TODO handle S_IDLE %i '%s'\n",code,c->resp_msg); [self nc_close: c]; break; case S_QUIT: [rec nntp_message: [NSString stringWithFormat: _(@"Closing connection: %i %s"), code,c->resp_msg]]; [self nc_free: c]; break; case S_CONNECT1: [rec nntp_message: [NSString stringWithFormat: _(@"New connection (%i total): %i %s"),num_active,code,c->resp_msg]]; [self nc_responseClear: c]; [self nc_writeCommand: c : "MODE READER"]; [self nc_setState: c : S_CONNECT2]; break; case S_CONNECT2: if (/*code!=500 && */code!=200 && code!=201) { if (num_active==1) { should_connect=0; /* TODO: think hard about how this should be handled */ [rec nntp_fail_connect: [NSString stringWithFormat: _(@"Unexpected response when connecting: %i %s"),code,c->resp_msg]]; } else { [rec nntp_message: [NSString stringWithFormat: _(@"Unexpected response when connecting: %i %s"),code,c->resp_msg]]; } [self nc_close: c]; break; } [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; lca_delta=-1.0; break; case S_DATE: if (code==111) [rec nntp_serverDate: c->resp_msg qid: q->qid]; else [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); break; case S_GROUP: if (code==211) { /* 211 n f l s group selected */ int num,first,last; c->cur_group=q->d.group; if (sscanf(c->resp_msg,"%i %i %i",&num,&first,&last)==3) [rec nntp_groupInfo: q->d.group : num : first : last qid: q->qid]; else [self nc_fail: c]; } else if (code==411) { /* 411 no such news group */ [rec nntp_groupInfo: q->d.group : -1 : -1 : -1 qid: q->qid]; } else [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); break; case S_SWITCH_GROUP: if (code==211) { c->cur_group=q->d.group; [self nc_responseClear: c]; [self nc_handleWhat: c]; } else { [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } break; case S_HEADER_RANGE1: if (code==224) { [self nc_responseWantData: c]; [self nc_setState: c : S_HEADER_RANGE2]; } else { [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } break; case S_HEADER_RANGE2: { char **list; int num; char *b,*d,*e; num=0; list=malloc(sizeof(char *)*(q->d.range.h-q->d.range.l+1)); d=b=c->resp_buf; e=b+c->resp_data_len; for (;bd.range.h-q->d.range.l+1; stat_header_bytes+=c->resp_data_len; #endif if (num) { [rec nntp_headerRange: q->d.range.group : q->d.range.l : q->d.range.h : num : list partial: NO qid: q->qid]; } free(list); [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); break; } case S_HEADER_ID1: if (code==221) { [self nc_responseWantData: c]; [self nc_setState: c : S_HEADER_ID2]; } else { [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } break; case S_HEADER_ID2: [rec nntp_headerId: q->d.msg.id data: c->resp_buf : c->resp_data_len qid: q->qid]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); break; case S_ARTICLE_RANGE1: if (code==220) { [self nc_responseWantData: c]; [self nc_setState: c : S_ARTICLE_RANGE2]; } else if (code==423) { [rec nntp_articleRange: q->d.range.group : q->d.range.l data: NULL:-1 qid: q->qid]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } else { [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } break; case S_ARTICLE_RANGE2: [rec nntp_articleRange: q->d.range.group : q->d.range.l data: c->resp_buf : c->resp_data_len qid: q->qid]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); break; case S_ARTICLE_ID1: if (code==220) { [self nc_responseWantData: c]; [self nc_setState: c : S_ARTICLE_ID2]; } else if (code==430) { [rec nntp_articleId: q->d.msg.id data: NULL:-1 qid: q->qid]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } else { [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } break; case S_ARTICLE_ID2: [rec nntp_articleId: q->d.msg.id data: c->resp_buf : c->resp_data_len qid: q->qid]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); break; case S_GROUP_LIST1: if (code==215) { [self nc_responseWantData: c]; [self nc_setState: c : S_GROUP_LIST2]; } else { [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } break; case S_GROUP_LIST2: [rec nntp_groupList: c->resp_buf : c->resp_data_len qid: q->qid]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); break; case S_POST_ARTICLE1: // fprintf(stderr,"POST_ARTICLE1: %3i %s\n",code,c->resp_msg); if (code!=340) { [rec nntp_postArticle: NO qid: q->qid]; [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } else { [self nc_responseClear: c]; [self nc_writeData: c : q->d.post.data : q->d.post.length]; q->d.post.data=NULL; q->d.post.length=0; [self nc_setState: c : S_POST_ARTICLE2]; } break; case S_POST_ARTICLE2: // fprintf(stderr,"POST_ARTICLE2: %3i %s\n",code,c->resp_msg); if (code==240) { [rec nntp_postArticle: YES qid: q->qid]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } else { [rec nntp_postArticle: NO qid: q->qid]; /* TODO: really do this here? */ [self nc_fail: c]; [self nc_responseClear: c]; [self nc_setState: c : S_IDLE]; queue_entry_clear(q); } break; default: fprintf(stderr,"unknown state %i\n",c->state); abort(); break; } } } -(void) nc_handleWhat: (con_t *)c { queue_entry_t *q=&c->cur; switch (q->what) { case WHAT_DATE: [self nc_setState: c : S_DATE]; [self nc_writeCommand: c : "DATE"]; break; case WHAT_GROUP_INFO: [self nc_setState: c : S_GROUP]; [self nc_writeCommand: c : "GROUP %s", group_names[q->d.group]]; break; case WHAT_HEADER_RANGE: if ([self nc_switchGroup: c : q->d.range.group]) break; [self nc_setState: c : S_HEADER_RANGE1]; [self nc_writeCommand: c : "XOVER %i-%i",q->d.range.l,q->d.range.h]; break; case WHAT_HEADER_ID: [self nc_setState: c : S_HEADER_ID1]; [self nc_writeCommand: c : "HEAD %s",q->d.msg.id]; break; case WHAT_ARTICLE_RANGE: if ([self nc_switchGroup: c : q->d.range.group]) break; [self nc_setState: c : S_ARTICLE_RANGE1]; [self nc_writeCommand: c : "ARTICLE %i",q->d.range.l]; break; case WHAT_ARTICLE_ID: [self nc_setState: c : S_ARTICLE_ID1]; [self nc_writeCommand: c : "ARTICLE %s",q->d.msg.id]; break; case WHAT_GROUP_LIST: [self nc_setState: c : S_GROUP_LIST1]; [self nc_writeCommand: c : "LIST"]; break; case WHAT_POST_ARTICLE: [self nc_setState: c : S_POST_ARTICLE1]; [self nc_writeCommand: c : "POST"]; break; default: fprintf(stderr,"unhandled q->what %i\n",q->what); break; } } -(void) nc_timeout { int i; con_t *c; // printf("timing out, closing one at %30.15g\n",Now()); for (i=0,c=cons;istate==S_IDLE) break; if (i==num_cons) { for (i=0,c=cons;istate!=S_NONE) break; } set_timeout=0; if (i!=num_cons) [self nc_close: c]; [self updateQueue]; } -(void) updateQueue { queue_entry_t *q; int i; int num_closing; int cur_work; int active; con_t *c; // fprintf(stderr," --- update: %i/%i/%i ---\n",num_idle,num_active,num_cons); for (c=cons,i=0;istate!=S_IDLE) continue; if (c->should_close) { [self nc_writeCommand: c : "QUIT"]; [self nc_setState: c : S_QUIT]; [self nc_update: c]; continue; } q=queue_get(queue); if (!q) continue; /* printf("%i in queue\n",queue->qe_num); printf("got pri %i what %i on %i/%i (p %i %i %i)\n", q->priority,q->what,i,c->sock, q->d.range.group,q->d.range.l,q->d.range.h);*/ c->cur=*q; if (q->what==WHAT_ARTICLE_RANGE) { c->cur.d.range.h=c->cur.d.range.l; q->d.range.l++; queue->total_work-=ESTIMATED_MSG_SIZE; /* TODO: yuck */ if (q->d.range.l>q->d.range.h) queue_delete_max(queue); } else { queue_delete_max(queue); } [self nc_handleWhat: c]; [self nc_update: c]; } num_closing=0; cur_work=0; for (i=0,c=cons;istate!=S_NONE && (c->should_close || c->state==S_QUIT)) num_closing++; if (c->state!=S_NONE && c->state!=S_IDLE && c->state!=S_QUIT) cur_work+=queue_work(&c->cur); } cur_work+=queue->total_work; if (num_active-num_closing>0) active=cur_work>work_limits[num_active-1-num_closing].work_limit; else active=!!cur_work; /* fprintf(stderr,"work=%i %i active=%i num_closing=%i\n",queue->total_work,cur_work,active,num_closing); fprintf(stderr,"%i %i %i %i\n",active,should_connect, Now()>lca_delta+last_connect_attempt,queue->total_work>work_limits[num_active].work_limit); fprintf(stderr,"update queue: approx. %i bytes left\n",queue->total_work);*/ if ( active && should_connect && num_activelca_delta+last_connect_attempt && cur_work>work_limits[num_active].work_limit && queue->qe_num>num_active ) { for (i=0,c=cons;istate==S_NONE) break; // printf("opening new\n"); last_connect_attempt=Now(); if ([self nc_connect: c]) { lca_delta=15.0; } else { if (!num_active) { should_connect=0; [rec nntp_fail_connect: last_error]; } else { if (!lca_delta) lca_delta=15.0; else if (lca_delta>90.0) lca_delta=90.0; else lca_delta+=15.0; } } } if (!active && (num_active-num_closing) && set_timeout==0) { // last_activity=Now(); [self performSelector: @selector(nc_timeout) withObject: [NSObject class] afterDelay: work_limits[num_active-1-num_closing].time_limit]; set_timeout=1; } else if (active && set_timeout==1) { set_timeout=0; [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(nc_timeout) object: [NSObject class]]; } // printf("done update\n"); #ifdef DEBUG_SCHEDULER [self _updateDebugScheduler]; #endif } @end @implementation NNTPServer +(int) getGroupNum: (const char *)group; { int i; char *c,**l; if (!group_names) { group_names=malloc(sizeof(char *)); if (!group_names) return -1; num_group_names=1; group_names[0]=NULL; } for (i=1;i=num_group_names) return NULL; return group_names[group]; } -init { return [self initWithHost: "localhost"]; } -initWithHost: (const char *)ahost { return [self initWithHost: ahost port: -1]; } -initWithHost: (const char *)ahost port: (int)aport { self=[super init]; if (!self) return nil; host=strdup(ahost); if (!host) [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; port=aport; protocol=-1; have_addr=0; return [self setup]; } -initWithAddr: (struct sockaddr_in *)aaddr port: (int)aport host: (const char *)ahost { self=[super init]; if (!self) return nil; if (host) host=strdup(ahost); else host=strdup(""); if (!host) [NSException raise: @"NNTPServer_Error" format: @"out of memory"]; port=aport; protocol=-1; have_addr=1; addr=*aaddr; return [self setup]; } -(void) dealloc { #ifdef XOVER_STATS printf("got %i/%i headers, %i bytes, %g bytes/msg\n", stat_num_headers,stat_num_expected_headers,stat_header_bytes, stat_header_bytes/(double)stat_num_headers); #endif [self killAllConnections]; DESTROY(runloop); DESTROY(runmodes); DESTROY(last_error); free(host); free(cons); queue_free(queue); [super dealloc]; } -(void) closeAllConnections { int i; con_t *c; for (c=cons,i=0;i)arec; { rec=arec; } -(void) enableConnect: (int)c { should_connect=c; if (c) lca_delta=-1.0; [self updateQueue]; } -(void) enableTimeout: (int)t { if (t) { set_timeout=0; } else { if (set_timeout==1) { [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(nc_timeout) object: [NSObject class]]; } set_timeout=-1; } } -(unsigned int) queueAdd: (queue_entry_t *)q : (int)priority { unsigned int qid; qid=queue_add(queue,priority,q); [self updateQueue]; return qid; } -(BOOL) cancelQid: (unsigned int)qid kill: (BOOL)kill { int i; queue_entry_t *qe; for (qe=queue->qe+1,i=0;iqe_num;i++,qe++) { if (qe->qid==qid) { queue_remove(queue,qe); [self updateQueue]; return YES; } } if (kill) { fprintf(stderr,"[NNTPServer -cancelQid: kill: YES] not implemented!\n"); } return NO; } -(unsigned int) getServerDate { queue_entry_t q; q.what=WHAT_DATE; return [self queueAdd: &q : 0]; } -(unsigned int) getGroupInfo: (int)group { queue_entry_t q; q.what=WHAT_GROUP_INFO; q.d.group=group; return [self queueAdd: &q : 0]; } -(unsigned int) getHeaderRange: (int)low : (int)high group: (int)group priority: (int)pri { queue_entry_t q; q.what=WHAT_HEADER_RANGE; q.d.range.group=group; q.d.range.l=low; q.d.range.h=high; return [self queueAdd: &q : pri]; } -(unsigned int) getHeaderById: (const char *)msg_id priority: (int)pri { queue_entry_t q; q.what=WHAT_HEADER_ID; q.d.msg.id=strdup(msg_id); /* TODO: handle error */ return [self queueAdd: &q : pri]; } -(unsigned int) getArticleRange: (int)low : (int)high group: (int)group priority: (int)pri { queue_entry_t q; q.what=WHAT_ARTICLE_RANGE; q.d.range.group=group; q.d.range.l=low; q.d.range.h=high; return [self queueAdd: &q : pri]; } -(unsigned int) getArticleById: (const char *)msg_id priority: (int)pri { queue_entry_t q; q.what=WHAT_ARTICLE_ID; q.d.msg.id=strdup(msg_id); /* TODO: handle error */ q.d.msg.bytes=1000; return [self queueAdd: &q : pri]; } -(unsigned int) getArticleById: (const char *)msg_id size: (int)bytes priority: (int)pri { queue_entry_t q; q.what=WHAT_ARTICLE_ID; q.d.msg.id=strdup(msg_id); /* TODO: handle error */ q.d.msg.bytes=bytes; return [self queueAdd: &q : pri]; } -(unsigned int) getGroupList: (int)priority { queue_entry_t q; q.what=WHAT_GROUP_LIST; return [self queueAdd: &q : priority]; } -(unsigned int) postArticle: (unsigned char *)data length: (int)length priority: (int)priority { queue_entry_t q; unsigned char *b,*c,*dst; int i,j; q.what=WHAT_POST_ARTICLE; for (b=data,i=length,j=0;i;b++,i--) if (b[0]=='\n') { j++; if (i>0 && b[1]=='.') j++; } j+=length; j+=5; dst=malloc(j); if (!dst) abort(); /* TODO: handle error */ for (b=data,c=dst,i=length;i;i--) { if (b[0]=='\n') *c++='\r'; *c++=*b++; if (i>1 && b[-1]=='\n' && b[0]=='.') *c++='.'; } *c++='\r'; *c++='\n'; *c++='.'; *c++='\r'; *c++='\n'; q.d.post.data=dst; q.d.post.length=j; return [self queueAdd: &q : priority]; } -(NSString *)qidDescription: (unsigned int)qid { int i; queue_entry_t *q=NULL; for (i=0;iqe+1,i=0;iqe_num;i++,q++) if (q->qid==qid) break; if (i==queue->qe_num) q=NULL; } if (!q) return [NSString stringWithFormat: @"qid %i",qid]; switch (q->what) { default: return [NSString stringWithFormat: @"qid %i %i",qid,q->what]; case WHAT_NONE: return @"NONE"; case WHAT_DATE: return @"DATE"; case WHAT_GROUP_INFO: return [NSString stringWithFormat: @"GROUP_INFO %s",group_names[q->d.group]]; case WHAT_HEADER_RANGE: return [NSString stringWithFormat: @"HEADER_RANGE %s %i-%i", group_names[q->d.range.group],q->d.range.l,q->d.range.h]; case WHAT_HEADER_ID: return [NSString stringWithFormat: @"HEADER_ID %s",q->d.msg.id]; case WHAT_ARTICLE_RANGE: return [NSString stringWithFormat: @"ARTICLE_RANGE %s %i-%i", group_names[q->d.range.group],q->d.range.l,q->d.range.h]; case WHAT_ARTICLE_ID: return [NSString stringWithFormat: @"ARTICLE_ID %s",q->d.msg.id]; case WHAT_GROUP_LIST: return @"GROUP_LIST"; case WHAT_POST_ARTICLE: return @"POST_ARTICLE"; } } @end #ifdef DEBUG_SCHEDULER #include #include #include #include static NSTextView *ds_tv; static NNTPServer *ds_server; static FILE *f; static NSString *last_string; @implementation NNTPServer (debug_scheduler) -(void) _updateDebugScheduler { NSMutableString *s; int i; con_t *c; queue_entry_t *qe; if (!ds_tv) { NSWindow *win; if (!queue->qe_num && !num_active) return; win=[[NSWindow alloc] initWithContentRect: NSMakeRect(10,64,500,500) styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask backing: NSBackingStoreRetained defer: YES]; ds_tv=[[NSTextView alloc] init]; [ds_tv setFont: [NSFont userFixedPitchFontOfSize: 0]]; [ds_tv setEditable: NO]; [[ds_tv textContainer] setWidthTracksTextView: YES]; [[ds_tv textContainer] setHeightTracksTextView: NO]; [[ds_tv textContainer] setContainerSize: NSMakeSize(1e6,1e6)]; [win setContentView: ds_tv]; RELEASE(ds_tv); ds_server=self; [win setTitle: @"NNTPServer debug"]; [win orderFront: self]; printf("created window: %p\n",win); f=fopen("debug_sched.txt","at"); } if (ds_server!=self) return; s=[[NSMutableString alloc] init]; [s appendString: [NSString stringWithFormat: @"num=%i idle=%i active=%i starting=%i\n\n", num_cons,num_idle,num_active,num_starting]]; for (i=0,c=cons;ivalid,c->sock, c->write_len,c->write_pending_ofs, c->resp,c->resp_code,c->resp_msg, c->resp_buf_len,c->resp_data_len, c->state,state_name[c->state], c->cur_group,c->should_close,c->reg_write, c->cur.priority,[self qidDescription: c->cur.qid]]]; } for (i=1,qe=queue->qe+1;i<=queue->qe_num;i++,qe++) { [s appendString: [NSString stringWithFormat: @"%3i: %3i %@\n",qe->qid,qe->priority,[self qidDescription: qe->qid]]]; } [ds_tv setString: s]; if ([s isEqual: last_string]) { DESTROY(s); } else { fputs("----\n",f); fputs([s cString],f); fflush(f); DESTROY(last_string); last_string=s; } } @end #endif LuserNET-0.4.2/NNTPSource.h100664 764 764 1055 10021217655 13446 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef NNTPSource_h #define NNTPSource_h @class NNTPServer; @class MsgDB; @class NSMutableDictionary; struct nntpsource_group_s { int idx; int last_num; }; @interface NNTPSource : NSObject { NNTPServer *server; MsgDB *mdb; msg_id_t mmid; int num_groups; struct nntpsource_group_s *groups; char *cur_host,*cur_sport; NSMutableDictionary *request_map; } -(void) postArticle: (unsigned char *)data length: (int)length sender: (NSObject *)sender; @end #endif LuserNET-0.4.2/NNTPSource.m100664 764 764 26217 10021217655 13502 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include "MsgDB.h" #include "NNTPSource.h" #include "GUISource.h" #include "NNTPSourceGUI.h" #include "NNTPServer.h" typedef struct nntpsource_group_s group_t; @implementation NNTPSource (private) -(void) recreateNNTPServer { const char *host,*sport; host=[mdb msg_getMetaHeader: "Host" : mmid]; sport=[mdb msg_getMetaHeader: "Port" : mmid]; if (cur_host && !strcmp(host,cur_host) && (sport==cur_sport || !strcmp(sport,cur_sport))) return; if (cur_host) free(cur_host); if (host) { cur_host=strdup(host); if (!cur_host) abort(); /* TODO */ } else cur_host=NULL; if (cur_sport) free(cur_sport); if (sport) { cur_sport=strdup(sport); if (!cur_sport) abort(); /* TODO */ } else cur_sport=NULL; if (server) { [server enableConnect: 0]; [server closeAllConnections]; DESTROY(server); } if (host) { if (sport && atoi(sport)>0 && atoi(sport)<65536) server=[[NNTPServer alloc] initWithHost: host port: atoi(sport)]; else server=[[NNTPServer alloc] initWithHost: host]; [server setReceiver: self]; [server enableConnect: 1]; } } -(void) syncGroupHeaders: (int)gnum { char buf1[32],buf2[32]; snprintf(buf1,sizeof(buf1),"G%i-num",gnum); snprintf(buf2,sizeof(buf2),"%i",groups[gnum].last_num); [mdb msg_setMetaHeader: buf1 value: buf2 : mmid]; } -(void) syncAllGroupHeaders { int i; char buf1[32],buf2[32]; for (i=0;i=first) first=groups[i].last_num+1; [[mdb notificationCenter] postNotificationName: MsgDB_LogMessageNotification object: mdb userInfo: [NSDictionary dictionaryWithObject: [NSString stringWithFormat: _(@"%i new messages in '%s', retrieving headers..."), last-first+1,[NNTPServer getGroupName: groups[i].idx]] forKey: @"Message"]]; // printf("requesting headers for %i-%i=%i\n",first,last,last-first+1); [server getHeaderRange: first : last group: group priority: 0]; } -(void) nntp_headerRange: (int)group : (int)first : (int)last : (int)num : (char **)list partial: (BOOL)partial qid: (unsigned int)qid { static const char *fields[8]= {0,"Real-Subject","Real-From","Real-Date","Real-Message-ID", "Real-References","Real-Bytes","Real-Lines"}; /* TODO: can't be static since different NNTPSource:s might have different mdb:s. should probably keep in an instance variable. */ int field_nums[8]; int i; int gnum; if (!num) return; // printf("got %i headers in\n",num); for (gnum=0;gnum */ #ifndef NNTPSourceGUI_h #define NNTPSourceGUI_h #include "MsgDB.h" #include "GUISource.h" #include "NNTPSource.h" @interface NNTPSource (GUI) @end #include "NNTPServer.h" @interface NNTPSource (private) -(void) recreateNNTPServer; -(void) syncGroupHeaders: (int)gnum; -(void) syncAllGroupHeaders; @end #endif LuserNET-0.4.2/NNTPSourceGUI.m100664 764 764 25513 10021217655 14045 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "autokeyviewchain.h" #include "NNTPSourceGUI.h" /* TODO: group browser */ @interface NNTPSource (GUI2) -(NSString *) getHostString; -(NSString *) getPortString; -(void) setHost: (NSString *)h port: (NSString *)p; -(int) numGroups; -(NSString *) groupName: (int)idx; -(int) groupLastMessage: (int)idx; -(void) addGroup: (NSString *)s lastMessage: (int)lmsg; -(void) removeGroup: (int)idx; -(void) queryGroup: (NSString *)g; @end @interface NNTPSourcePropertiesController : NSWindowController { NNTPSource *ns; NSTextField *f_host,*f_port; NSTextField *f_group,*f_lastmsg; NSTableView *group_list; NSTableColumn *c_name,*c_lastmsg; } - initWithNNTPSource: (NNTPSource *)n; @end @implementation NNTPSourcePropertiesController -(void) windowWillClose: (NSNotification *)n { [self autorelease]; } -(void) done: (id)sender { [ns setHost: [f_host stringValue] port: [f_port stringValue]]; [self close]; } -(void) addGroup: (id)sender { [ns addGroup: [f_group stringValue] lastMessage: [f_lastmsg intValue]]; [group_list reloadData]; } -(void) removeGroup: (id)sender { if ([group_list selectedRow]!=-1) { [ns removeGroup: [group_list selectedRow]]; [group_list reloadData]; } } -(void) queryGroup: (id)sender { /* update host and port first to avoid confusing behaviour */ [ns setHost: [f_host stringValue] port: [f_port stringValue]]; [ns queryGroup: [f_group stringValue]]; [f_lastmsg setStringValue: _(@"")]; } -(int) numberOfRowsInTableView: (NSTableView *)tv { return [ns numGroups]; } -(id) tableView: (NSTableView *)tv objectValueForTableColumn: (NSTableColumn *)tc row: (int)row { if (tc==c_name) return [ns groupName: row]; else return [NSNumber numberWithInt: [ns groupLastMessage: row]]; } - initWithNNTPSource: (NNTPSource *)n { NSPanel *win; win=[[NSPanel alloc] initWithContentRect: NSMakeRect(100,100,320,320) styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask backing: NSBackingStoreRetained defer: YES]; if (!(self=[super initWithWindow: win])) return nil; ASSIGN(ns,n); { GSVbox *vbox; vbox=[[GSVbox alloc] init]; [vbox setBorder: 4]; [vbox setDefaultMinYMargin: 4]; [vbox setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; { GSHbox *hbox; NSButton *b; hbox=[[GSHbox alloc] init]; [hbox setDefaultMinXMargin: 4]; [hbox setAutoresizingMask: NSViewMinXMargin]; b=[[NSButton alloc] init]; [b setTitle: _(@"Done")]; [b setTarget: self]; [b setAction: @selector(done:)]; [b setKeyEquivalent: @"\r"]; [b sizeToFit]; [hbox addView: b]; [b release]; [vbox addView: hbox enablingYResizing: NO]; [hbox release]; } [vbox addSeparator]; { GSHbox *hbox; NSButton *b; NSTextField *tf; hbox=[[GSHbox alloc] init]; [hbox setDefaultMinXMargin: 4]; [hbox setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; { GSVbox *vb; vb=[[GSVbox alloc] init]; [vb setDefaultMinYMargin: 4]; [vb setAutoresizingMask: NSViewMinYMargin|NSViewWidthSizable]; tf=[[NSTextField alloc] init]; [tf setStringValue: @""]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vb addView: tf]; f_lastmsg=tf; tf=[[NSTextField alloc] init]; [tf setStringValue: _(@"Last message (use query):")]; [tf setEditable: NO]; [tf setDrawsBackground: NO]; [tf setBordered: NO]; [tf setBezeled: NO]; [tf setSelectable: NO]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vb addView: tf enablingYResizing: NO]; [tf release]; tf=[[NSTextField alloc] init]; [tf setStringValue: @""]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vb addView: tf]; f_group=tf; tf=[[NSTextField alloc] init]; [tf setStringValue: _(@"Group name:")]; [tf setEditable: NO]; [tf setDrawsBackground: NO]; [tf setBordered: NO]; [tf setBezeled: NO]; [tf setSelectable: NO]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vb addView: tf enablingYResizing: NO]; [tf release]; [hbox addView: vb]; [vb release]; } { GSVbox *vb; vb=[[GSVbox alloc] init]; [vb setDefaultMinYMargin: 4]; [vb setAutoresizingMask: NSViewMinYMargin]; b=[[NSButton alloc] init]; [b setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [b setTitle: _(@"Add")]; [b setTarget: self]; [b setAction: @selector(addGroup:)]; [b sizeToFit]; [vb addView: b enablingYResizing: NO]; [b release]; b=[[NSButton alloc] init]; [b setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [b setTitle: _(@"Remove")]; [b setTarget: self]; [b setAction: @selector(removeGroup:)]; [b sizeToFit]; [vb addView: b enablingYResizing: NO]; [b release]; b=[[NSButton alloc] init]; [b setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [b setTitle: _(@"Query")]; [b setTarget: self]; [b setAction: @selector(queryGroup:)]; [b sizeToFit]; [vb addView: b enablingYResizing: NO]; [b release]; [hbox addView: vb enablingXResizing: NO]; [vb release]; } [vbox addView: hbox enablingYResizing: NO]; [hbox release]; } [vbox addSeparator]; { NSScrollView *sv; sv=[[NSScrollView alloc] init]; [sv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [sv setHasVerticalScroller: YES]; [sv setHasHorizontalScroller: NO]; [sv setBorderType: NSBezelBorder]; c_name=[[NSTableColumn alloc] initWithIdentifier: @"Group"]; [[c_name headerCell] setStringValue: _(@"Group")]; [c_name setEditable: NO]; [c_name setResizable: YES]; [c_name setWidth: 200]; c_lastmsg=[[NSTableColumn alloc] initWithIdentifier: @"Last msg"]; [[c_lastmsg headerCell] setStringValue: _(@"Last msg")]; [c_lastmsg setEditable: NO]; [c_lastmsg setResizable: YES]; [c_lastmsg setWidth: 32]; group_list=[[NSTableView alloc] initWithFrame: [[sv contentView] frame]]; [group_list setAllowsColumnReordering: YES]; [group_list setAllowsColumnResizing: YES]; [group_list setAllowsMultipleSelection: NO]; [group_list setAllowsColumnSelection: NO]; [group_list addTableColumn: c_name]; [group_list addTableColumn: c_lastmsg]; [group_list setDataSource: self]; [group_list setDelegate: self]; // [group_list setDoubleAction: @selector(openMessage:)]; [sv setDocumentView: group_list]; [vbox addView: sv]; [sv release]; } [vbox addSeparator]; { NSTextField *tf; tf=[[NSTextField alloc] init]; [tf setStringValue: [ns getPortString]]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vbox addView: tf enablingYResizing: NO]; f_port=tf; tf=[[NSTextField alloc] init]; [tf setStringValue: _(@"Port:")]; [tf setEditable: NO]; [tf setDrawsBackground: NO]; [tf setBordered: NO]; [tf setBezeled: NO]; [tf setSelectable: NO]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vbox addView: tf enablingYResizing: NO]; [tf release]; tf=[[NSTextField alloc] init]; [tf setStringValue: [ns getHostString]]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vbox addView: tf enablingYResizing: NO]; f_host=tf; tf=[[NSTextField alloc] init]; [tf setStringValue: _(@"Host:")]; [tf setEditable: NO]; [tf setDrawsBackground: NO]; [tf setBordered: NO]; [tf setBezeled: NO]; [tf setSelectable: NO]; [tf sizeToFit]; [tf setAutoresizingMask: NSViewWidthSizable]; [vbox addView: tf enablingYResizing: NO]; [tf release]; } [win setContentView: vbox]; [vbox release]; } [win setTitle: _(@"NNTPSource properties")]; [win setDelegate: self]; [win autoSetupKeyViewChain]; [win release]; return self; } -(void) dealloc { DESTROY(f_host); DESTROY(f_port); DESTROY(f_group); DESTROY(f_lastmsg); DESTROY(c_name); DESTROY(c_lastmsg); DESTROY(group_list); DESTROY(ns); [super dealloc]; } @end #include "NNTPServer.h" @implementation NNTPSource (GUI) -(NSString *) sourceType { return @"NNTP"; } -(NSString *) sourceName { const char *c=[mdb msg_getMetaHeader: "Host" : mmid]; if (c) return [NSString stringWithCString: c]; else return _(@""); } -(void) displayPropertiesWindow { NNTPSourcePropertiesController *w; w=[[NNTPSourcePropertiesController alloc] initWithNNTPSource: self]; [w showWindow: self]; } @end @implementation NNTPSource (GUI2) -(NSString *) getHostString { const char *c=[mdb msg_getMetaHeader: "Host" : mmid]; if (c) return [NSString stringWithCString: c]; else return @""; } -(void) setHost: (NSString *)h port: (NSString *)p { [mdb msg_setMetaHeader: "Host" value: [h cString] : mmid]; [mdb msg_setMetaHeader: "Port" value: [p cString] : mmid]; [self recreateNNTPServer]; /* this causes the return from sourceName to change */ [[mdb notificationCenter] postNotificationName: MsgDB_SourceChangeNotification object: mdb]; } -(NSString *) getPortString { const char *c=[mdb msg_getMetaHeader: "Port" : mmid]; if (c) return [NSString stringWithCString: c]; else return @""; } -(int) numGroups { return num_groups; } -(NSString *) groupName: (int)idx { return [NSString stringWithCString: [NNTPServer getGroupName: groups[idx].idx]]; } -(int) groupLastMessage: (int)idx { return groups[idx].last_num; } -(void) addGroup: (NSString *)s lastMessage: (int)lmsg { char buf[32]; int i,idx; const char *name; if (![s length]) return; name=[s cString]; idx=[NNTPServer getGroupNum: name]; for (i=0;i>>ÿ;;;ÿ999ÿ:::ÿ555ÿ<<<ÿ888ÿ:::ÿKKKÿ€€€ÿ~~~ÿ———ÿ³³³ÿ±±±ÿ§§§ÿ²²²ÿŒŒŒÿÿÿÿÿ›››ÿÓÓÓÿŒŒŒÿ]]]ÿ999ÿÏ› nC +++ç000ÿ111ÿ...ÿ000ÿ;;;ÿWWWÿ{{{ÿ™™™ÿ¢¢¢ÿ•••ÿjjjÿžžžÿªªªÿ°°°ÿ¿¿¿ÿÃÃÃÿÆÆÆÿÇÇÇÿ™™™ÿçççÿßßßÿ›››ÿ‹‹‹ÿŽŽŽÿfffÿ444ùª vI& §...ÿ000ÿ...ÿ;;;ÿyyyÿ©©©ÿ‰‰‰ÿ£££ÿ°°°ÿ¶¶¶ÿ©©©ÿ¹¹¹ÿ¾¾¾ÿÃÃÃÿÉÉÉÿÉÉÉÿÉÉÉÿÉÉÉÿÿ½½½ÿÿÿÿÿ×××ÿ³³³ÿwwwÿxxxÿRRRÿ ® tS/W555ÿ222ÿNNNÿoooÿŠŠŠÿ   ÿ¦¦¦ÿ···ÿ¾¾¾ÿ¿¿¿ÿ¾¾¾ÿÁÁÁÿÃÃÃÿÆÆÆÿÊÊÊÿÉÉÉÿÈÈÈÿÇÇÇÿºººÿ°°°ÿÿÿÿÿãããÿçççÿ¹¹¹ÿtttÿSSSÿ™ƒa9 ;;;ÿ€€€ÿ§§§ÿ¡¡¡ÿÿ±±±ÿµµµÿ½½½ÿ¿¿¿ÿÁÁÁÿÃÃÃÿÅÅÅÿÆÆÆÿÈÈÈÿÉÉÉÿÈÈÈÿÇÇÇÿÇÇÇÿÄÄÄÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨¨¨ÿRRRÿ$$$ç•gC#FFF×ÿ²²²ÿ±±±ÿ²²²ÿµµµÿ»»»ÿ¿¿¿ÿÁÁÁÿÃÃÃÿÃÃÃÿÄÄÄÿÆÆÆÿÇÇÇÿÇÇÇÿÈÈÈÿÆÆÆÿÄÄÄÿÀÀÀÿvvvÿïïïÿÿÿÿÿ×××ÿ¿¿¿ÿÿRRRÿ777ù ˆ sO/ 888–––ÿ¯¯¯ÿ···ÿ¹¹¹ÿ»»»ÿ¼¼¼ÿ½½½ÿ¿¿¿ÿÃÃÃÿÃÃÃÿÄÄÄÿÄÄÄÿÄÄÄÿÄÄÄÿÃÃÃÿ¿¿¿ÿ»»»ÿ¹¹¹ÿªªªÿ···ÿÿÿÿÿÏÏÏÿÇÇÇÿuuuÿ‚‚‚ÿOOOÿ¯ Z;///G¯¯¯ÿ¶¶¶ÿ···ÿ¸¸¸ÿºººÿºººÿ¼¼¼ÿ»»»ÿ¼¼¼ÿ»»»ÿ½½½ÿ½½½ÿ¼¼¼ÿ»»»ÿ»»»ÿ¸¸¸ÿ¶¶¶ÿ´´´ÿ²²²ÿ€€€ÿûûûÿÛÛÛÿ×××ÿ———ÿHHHÿEEEÿÛ gI*”””ײ²²ÿ²²²ÿ³³³ÿµµµÿ¶¶¶ÿ¶¶¶ÿ¶¶¶ÿ···ÿ···ÿ¶¶¶ÿµµµÿ···ÿ¸¸¸ÿºººÿ¸¸¸ÿ¶¶¶ÿ¶¶¶ÿ¥¥¥ÿVVVÿÿÿÿÿßßßÿŸŸŸÿìììÿ§§§ÿxxxÿ"""ì †rS3vvv«¯¯¯ÿ®®®ÿ¯¯¯ÿ°°°ÿ±±±ÿ²²²ÿ³³³ÿµµµÿµµµÿ±±±ÿ±±±ÿ²²²ÿ´´´ÿµµµÿ¨¨¨ÿeeeÿÿ­­­ÿÁÁÁÿÿÿÿÿïïïÿ•••ÿ¶¶¶ÿfffÿOOOÿEEEùœ \: NNNs¬¬¬ÿ«««ÿ¬¬¬ÿ«««ÿªªªÿªªªÿ«««ÿ¬¬¬ÿ«««ÿ©©©ÿ£££ÿ‡‡‡ÿnnnÿ|||ÿ¹¹¹ÿïïïÿ÷÷÷ÿ×××ÿÄÄÄÿ–––ÿÇÇÇÿ€€€ÿ¯¯¯ÿkkkÿAAAÿ???ÿ³ aA$«««ÿ©©©ÿ©©©ÿ¥¥¥ÿ¤¤¤ÿ¥¥¥ÿ§§§ÿÿ€€€ÿ‰‰‰ÿÿçççÿãããÿØØØÿ¿¿¿ÿ¨¨¨ÿ˜˜˜ÿšššÿxxxÿ”””ÿùùùÿ¡¡¡ÿ±±±ÿÒÒÒÿ¨¨¨ÿZZZÿ"""å kM-ß   ÿžžžÿžžžÿ¡¡¡ÿŒŒŒÿ]]]ÿ¡¡¡ÿ£££ÿŽŽŽÿžžžÿôôôÿ®®®ÿtttÿŠŠŠÿ‡‡‡ÿgggÿmmmÿÕÕÕÿÉÉÉÿÿÿÿÿÿÿÿÿÿÿÿÿôôôÿ“““ÿZZZÿ000ú ˆ W6 aaaŸ™™™ÿ˜˜˜ÿ™™™ÿfffÿ¡¡¡ÿºººÿÿÆÆÆÿõõõÿ½½½ÿïïïÿ»»»ÿ€€€ÿ¤¤¤ÿ­­­ÿàààÿ÷÷÷ÿÿÿÿÿñññÿ÷÷÷ÿÏÏÏÿ«««ÿÿiiiÿ]]]ÿ)))ÿ  ^> !!!7ŠŠŠÿƒƒƒÿ‚‚‚ÿ»»»ÿ’’’ÿ±±±ÿÂÂÂÿêêêÿõõõÿÄÄÄÿ¿¿¿ÿÏÏÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿºººÿiiiÿ···ÿ‡‡‡ÿÒÒÒÿvvvÿ¥¥¥ÿqqqÿ666ÿË fI+ ~~~ÿ‘‘‘ÿuuuÿrrrÿ‹‹‹ÿƒƒƒÿ———ÿáááÿõõõÿìììÿºººÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðððÿ¶¶¶ÿšššÿ®®®ÿ†††ÿƒƒƒÿ¼¼¼ÿeeeÿ———ÿ‚‚‚ÿmmmÿ)))é pU6 kkk¿TTTÿ]]]ÿuuuÿ^^^ÿ¹¹¹ÿÏÏÏÿãããÿÔÔÔÿüüüÿžžžÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿ’’’ÿÿ‘‘‘ÿƒƒƒÿ———ÿËËËÿ÷÷÷ÿÿÿÿÿ±±±ÿÿOOOÿ ˆ aA%!!!oKKKÿUUUÿsssÿ§§§ÿ¶¶¶ÿ™™™ÿvvvÿ­­­ÿýýýÿ–––ÿïïïÿÿÿÿÿëëëÿÃÃÃÿ¿¿¿ÿ———ÿŸŸŸÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¶¶¶ÿsssÿEEEÿ¹ hL/GSSSÿzzzÿŒŒŒÿŽŽŽÿ’’’ÿ³³³ÿ½½½ÿ¹¹¹ÿŠŠŠÿšššÿÿ§§§ÿ»»»ÿÃÃÃÿïïïÿþþþÿöööÿÆÆÆÿÚÚÚÿÿÿÿÿþþþÿêêêÿòòòÿáááÿˆˆˆÿ555ÿÞ oW: TTTÏ^^^ÿWWWÿ†††ÿÄÄÄÿ®®®ÿ………ÿrrrÿ‹‹‹ÿ»»»ÿïïïÿÿÿÿÿüüüÿØØØÿÀÀÀÿÍÍÍÿeeeÿïïïÿ÷÷÷ÿÂÂÂÿƒƒƒÿ¼¼¼ÿ………ÿtttÿ˜˜˜ÿbbbÿ"""â Œ zbG+111ŸcccÿŒŒŒÿ•••ÿ„„„ÿÿ‡‡‡ÿÇÇÇÿÿÿÿÿöööÿÔÔÔÿÄÄÄÿÀÀÀÿåååÿúúúÿóóóÿËËËÿ²²²ÿ–––ÿDDDÿ———ÿÿ¤¤¤ÿlllÿ€€€ÿcccÿÿ¥ ‰mQ2_yyyÿŽŽŽÿrrrÿ«««ÿÏÏÏÿÿÿÿÿÿÿÿÿÿÿÿÿöööÿàààÿùùùÿóóóÿÍÍÍÿÿÿÿÿíííÿIIIÿÅÅÅÿ„„„ÿwwwÿÀÀÀÿÃÃÃÿÛÛÛÿÜÜÜÿ¥¥¥ÿ‰‰‰ÿ>>>ÿÍ– uW8|||ûŸŸŸÿ×××ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ˜˜˜ÿÒÒÒÿsssÿñññÿÔÔÔÿÿÿÿÿÔÔÔÿ¹¹¹ÿ’’’ÿ```ÿÙÙÙÿ¡¡¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿªªªÿ}}}ÿ999×› vY<# §§§ÓÿÿÿÿÿÿÿÿÿÿÿÿüüüÿÚÚÚÿãããÿÜÜÜÿ–––ÿ•••ÿtttÿLLLÿ¯¯¯ÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÄÄÄÿéééÿöööÿÿÿÿÿÿÿÿÿÿÿÿÿöööÿ£££ÿˆˆˆÿPPPÿ"""› oV?(ÿÿÿÿûûûÿýýýÿ¬¬¬ÿ³³³ÿZZZÿÿŸŸŸÿÛÛÛÿÖÖÖÿÅÅÅÿfffÿÏÏÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøÿÖÖÖÿÓÓÓÿâââÿåååÿóóóÿyyyÿ€aTA,////óóóÿÛÛÛÿÈÈÈÿŸŸŸÿSSSÿ™™™ÿÆÆÆÿõõõÿÿÿÿÿÞÞÞÿøøøÿüüüÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿéééÿÒÒÒÿÎÎÎÿâââÿøøøÿóóóÿÝÝÝÿ¦¦¦ÿ|||ÿŒŒŒÿBBBŸc RA-ËËË÷NNNÿ„„„ÿÈÈÈÿ¿¿¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿêêêÿäääÿÖÖÖÿÌÌÌÿáááÿíííÿÜÜÜÿ¿¿¿ÿZZZÿVVVÿ]]]ÿrrrÿVVVÿ^^^ÿ@@@˜_ PA/   §ÍÍÍÿçççÿñññÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÙÙÙÿÑÑÑÿàààÿåååÿßßßÿºººÿÿEEEÿEEEÿXXXÿRRRÿ\\\ÿ¸¸¸ÿÄÄÄÿÆÆÆÿÏÏÏÿuuuâ))){VK=)ccccüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóóóÿâââÿÌÌÌÿæææÿÓÓÓÿtttÿEEEÿCCCÿCCCÿIIIÿZZZÿlllÿ………ÿ···ÿÐÐÐÿàààÿßßßÿqhhƒ-))/ PLLL; 3333óóóóÿÿÿÿøøøÿåååÿÃÃÃÿ´´´ÿÿPPPÿCCCÿCCCÿKKKÿ___ÿ~~~ÿ‰‰‰ÿ———ÿ¼¼¼ÿðððÿéééÿÛÛÛï®®®¯oooo\LLLLL3" ËËËËûûûÿ´´´ÿÿ]]]ÿ]]]ÿbbbÿbbbÿrrrÿpppÿ———ÿ···ÿÂÂÂÿÕÕÕÿâââÿÚÚÚÿ¤¤¤ÿ•‘‘ÿoll¯LLL3+ ————ÅÅÅÿsssÿvvvÿ}}}ÿtttÿ~~~ÿžžžÿ¾¾¾ÿãããÿšššÿqqqÿZZZÿcccÿsssÿzzzÿ“““ÿ‡††î523?GGGG–––ÿÿ‹‹‹ÿŒŒŒÿ„„„ÿaaaÿVVVÿeeeÿaaaÿvvvÿÇÇÇÿçççÿøøøÿÜÜÜãëëëëÇÇÇÇssssààà÷vvvÿ†††ÿ‹ˆ‰ÿž››ÿÄÄÄÿÃÃÃßâââï‘‘‘Ÿ¹¹¹¿rrrsggggWWWW####++++vvv‡p]^—)7YXY[ 00$ª$$²$º(R ü€' ü€'LuserNET-0.4.2/PrefBox.h100664 764 764 1564 10021217655 13060 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef PrefBox_h #define PrefBox_h @class NSView,NSButton,NSString; @protocol PrefBox -(void) setupButton: (NSButton *)b; /* Called when the PrefBox is added to a preferences panel. It should add a label or an image or something to the button. */ -(void) willHide; /* Called just before another PrefBox will be displayed if this one is currently displayed.*/ -(NSView *) willShow; /* Called before this PrefBox will be displayed. Should return the top-level NSView that should be displayed. (This view should probably be cached, but creating it on demand is nice.) */ -(void) save; -(void) revert; /* Called on all PrefBox:s when the user clicks the save or revert button. */ -(NSString *) name; /* Return the name that is displayed in the header of the NSBox when this PrefBox is displayed. */ @end #endif LuserNET-0.4.2/Pref_MessageViewing.h100664 764 764 1045 7442772426 15375 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef Pref_MessageView_h #define Pref_MessageView_h #include "PrefBox.h" @class GSVbox,NSTextField,NSFont; @interface Pref_MessageViewing : NSObject { /* only top is directly retained */ GSVbox *top; NSButton *b_ColorMessages,*b_IntelligentScroll; NSTextField *f_AutoDownloadSizeLimit; NSTextField *f_font1,*f_font2,*f_cur; } +(int) autoDownloadSizeLimit; +(BOOL) colorMessages; +(BOOL) intelligentScroll; +(NSFont *) font1; +(NSFont *) font2; @end #endif LuserNET-0.4.2/Pref_MessageViewing.m100664 764 764 16032 10021217655 15425 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include "Pref_MessageViewing.h" #define ColorMessagesKey @"ColorMessages" #define MessageFont1Key @"MessageFont1" #define MessageFont1SizeKey @"MessageFont1Size" #define MessageFont2Key @"MessageFont2" #define MessageFont2SizeKey @"MessageFont2Size" #define IntelligentScrollKey @"IntelligentScroll" #define AutoDownloadSizeLimitKey @"AutoDownloadSizeLimit" static NSUserDefaults *sd; @implementation Pref_MessageViewing +(void) initialize { if (!sd) sd=[NSUserDefaults standardUserDefaults]; } +(int) autoDownloadSizeLimit { if ([sd objectForKey: AutoDownloadSizeLimitKey]) return [sd integerForKey: AutoDownloadSizeLimitKey]; else return 64*1024; } +(BOOL) colorMessages { if (![sd objectForKey: ColorMessagesKey] || [sd boolForKey: ColorMessagesKey]) return YES; else return NO; } +(BOOL) intelligentScroll { if (![sd objectForKey: IntelligentScrollKey] || [sd boolForKey: IntelligentScrollKey]) return YES; else return NO; } +(NSFont *) font1 { NSString *s; double size; NSFont *f; size=[sd floatForKey: MessageFont1SizeKey]; f=nil; if ((s=[sd stringForKey: MessageFont1Key])) { f=[NSFont fontWithName: s size: size]; } if (!f) f=[NSFont userFontOfSize: size]; return f; } +(NSFont *) font2 { NSString *s; double size; NSFont *f; size=[sd floatForKey: MessageFont2SizeKey]; f=nil; if ((s=[sd stringForKey: MessageFont2Key])) { f=[NSFont fontWithName: s size: size]; } if (!f) f=[NSFont userFixedPitchFontOfSize: size]; return f; } -(void) save { NSUserDefaults *sd=[NSUserDefaults standardUserDefaults]; BOOL b; if (!top) return; b=[b_ColorMessages state]?YES:NO; [sd setBool: b forKey: ColorMessagesKey]; b=[b_IntelligentScroll state]?YES:NO; [sd setBool: b forKey: IntelligentScrollKey]; [sd setInteger: [f_AutoDownloadSizeLimit intValue] forKey: AutoDownloadSizeLimitKey]; [sd setFloat: [[f_font1 font] pointSize] forKey: MessageFont1SizeKey]; [sd setObject: [[f_font1 font] fontName] forKey: MessageFont1Key]; [sd setFloat: [[f_font2 font] pointSize] forKey: MessageFont2SizeKey]; [sd setObject: [[f_font2 font] fontName] forKey: MessageFont2Key]; } -(void) revert { NSFont *f; if ([Pref_MessageViewing colorMessages]) [b_ColorMessages setState: 1]; else [b_ColorMessages setState: 0]; if ([Pref_MessageViewing intelligentScroll]) [b_IntelligentScroll setState: 1]; else [b_IntelligentScroll setState: 0]; [f_AutoDownloadSizeLimit setIntValue: [Pref_MessageViewing autoDownloadSizeLimit]]; f=[Pref_MessageViewing font1]; [f_font1 setStringValue: [NSString stringWithFormat: @"%@ %0.1f",[f fontName],[f pointSize]]]; [f_font1 setFont: f]; f=[Pref_MessageViewing font2]; [f_font2 setStringValue: [NSString stringWithFormat: @"%@ %0.1f",[f fontName],[f pointSize]]]; [f_font2 setFont: f]; } -(NSString *) name { return _(@"Message viewing"); } -(void) setupButton: (NSButton *)b { [b setTitle: _(@"Message\nviewing")]; [b sizeToFit]; } -(void) willHide { } -(NSView *) willShow { if (!top) { top=[[GSVbox alloc] init]; [top setDefaultMinYMargin: 4]; { NSButton *b; b_ColorMessages=b=[[NSButton alloc] init]; [b setTitle: _(@"Color lines in messages based on quoting depth.")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [top addView: b enablingYResizing: NO]; DESTROY(b); b_IntelligentScroll=b=[[NSButton alloc] init]; [b setTitle: _(@"Scroll intelligently (ie. skip quoted sections and signatures).")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [top addView: b enablingYResizing: NO]; DESTROY(b); } [top addSeparator]; { NSTextField *f; NSButton *b; GSHbox *hb; hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Automatically download messages smaller than:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); f_AutoDownloadSizeLimit=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hb addView: f enablingXResizing: YES]; DESTROY(f); [top addView: hb enablingYResizing: NO]; DESTROY(hb); [top addSeparator]; hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Message font #2:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); f_font2=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [f setEditable: NO]; [hb addView: f enablingXResizing: YES]; DESTROY(f); b=[[NSButton alloc] init]; [b setTitle: _(@"Pick font...")]; [b setTarget: self]; [b setAction: @selector(pickFont2:)]; [b sizeToFit]; [hb addView: b enablingXResizing: NO]; DESTROY(b); [top addView: hb enablingYResizing: NO]; DESTROY(hb); hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Message font #1:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); f_font1=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [f setEditable: NO]; [hb addView: f enablingXResizing: YES]; DESTROY(f); b=[[NSButton alloc] init]; [b setTitle: _(@"Pick font...")]; [b setTarget: self]; [b setAction: @selector(pickFont1:)]; [b sizeToFit]; [hb addView: b enablingXResizing: NO]; DESTROY(b); [top addView: hb enablingYResizing: NO]; DESTROY(hb); } [self revert]; } return top; } -(void) dealloc { DESTROY(top); [super dealloc]; } -(void) _pickFont { NSFontManager *fm=[NSFontManager sharedFontManager]; [fm setSelectedFont: [f_cur font] isMultiple: NO]; [fm orderFrontFontPanel: self]; } -(void) pickFont1: (id)sender { f_cur=f_font1; [self _pickFont]; } -(void) pickFont2: (id)sender { f_cur=f_font2; [self _pickFont]; } -(void) changeFont: (id)sender { NSFont *f; if (!f_cur) return; f=[sender convertFont: [f_cur font]]; if (!f) return; [f_cur setStringValue: [NSString stringWithFormat: @"%@ %0.1f",[f fontName],[f pointSize]]]; [f_cur setFont: f]; } @end LuserNET-0.4.2/Pref_Posting.h100664 764 764 1066 10021217655 14107 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef Pref_Posting_h #define Pref_Posting_h #include "PrefBox.h" @class GSVbox,NSButton,NSTextField,NSPopUpButton; @interface Pref_Posting : NSObject { /* only top is directly retained */ GSVbox *top; NSButton *b_PostQuotedPrintable; NSTextField *f_PostingName,*f_FromAddress; NSTextField *f_SignatureValue; NSPopUpButton *b_SignatureType; } +(NSString *) postingSignature; +(NSString *) postingName; +(NSString *) fromAddress; +(BOOL) postQuotedPrintable; @end #endif LuserNET-0.4.2/Pref_Posting.m100664 764 764 15760 10021217655 14142 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include "Pref_Posting.h" #define PostingNameKey @"PostingName" #define FromAddressKey @"FromAddress" #define PostQuotedPrintableKey @"PostQuotedPrintable" #define SignatureTypeKey @"SignatureType" #define SignatureValueKey @"SignatureValue" #define SIGNATURE_LITERAL 0 #define SIGNATURE_FILE 1 #define SIGNATURE_PIPE 2 #define SIGNATURE_MAX 2 static NSUserDefaults *sd; @implementation Pref_Posting +(void) initialize { if (!sd) sd=[NSUserDefaults standardUserDefaults]; } +(NSString *) postingSignature { int type=[sd integerForKey: SignatureTypeKey]; NSString *s; s=[sd stringForKey: SignatureValueKey]; if (!s || ![s length]) return nil; switch (type) { default: case SIGNATURE_LITERAL: return s; case SIGNATURE_FILE: { NSString *sig=[NSString stringWithContentsOfFile: s]; if (sig) return sig; return [NSString stringWithFormat: _(@"Unable to read signature from '%@'.\n"),s]; } case SIGNATURE_PIPE: { FILE *p=popen([s cString],"r"); char buf[256]; int l; NSMutableString *sig=[[NSMutableString alloc] init]; if (!p) return [NSString stringWithFormat: _(@"Unable to get signature by running '%@'.\n"),s]; while ((l=fread(buf,1,sizeof(buf)-1,p))>0) { buf[l]=0; [sig appendString: [NSString stringWithCString: buf]]; } pclose(p); return AUTORELEASE(sig); } } } +(NSString *) fromAddress { return [sd stringForKey: FromAddressKey]; } +(NSString *) postingName { return [sd stringForKey: PostingNameKey]; } +(BOOL) postQuotedPrintable { if (![sd objectForKey: PostQuotedPrintableKey] || [sd boolForKey: PostQuotedPrintableKey]) return YES; else return NO; } -(void) save { NSUserDefaults *sd=[NSUserDefaults standardUserDefaults]; BOOL b; if (!top) return; b=[b_PostQuotedPrintable state]?YES:NO; [sd setBool: b forKey: PostQuotedPrintableKey]; [sd setObject: [f_FromAddress stringValue] forKey: FromAddressKey]; [sd setObject: [f_PostingName stringValue] forKey: PostingNameKey]; [sd setInteger: [b_SignatureType indexOfSelectedItem] forKey: SignatureTypeKey]; [sd setObject: [f_SignatureValue stringValue] forKey: SignatureValueKey]; } -(void) revert { if ([Pref_Posting postQuotedPrintable]) [b_PostQuotedPrintable setState: 1]; else; [b_PostQuotedPrintable setState: 0]; [f_FromAddress setStringValue: [Pref_Posting fromAddress]]; [f_PostingName setStringValue: [Pref_Posting postingName]]; { int idx=[sd integerForKey: SignatureTypeKey]; if (idx<0 || idx>SIGNATURE_MAX) idx=0; [f_SignatureValue setStringValue: [sd stringForKey: SignatureValueKey]]; [b_SignatureType selectItemAtIndex: idx]; } } -(NSString *) name { return _(@"Posting"); } -(void) setupButton: (NSButton *)b { [b setTitle: _(@"Posting")]; [b sizeToFit]; } -(void) willHide { } -(void) _signatureBrowse: (id)sender { NSOpenPanel *op=[NSOpenPanel openPanel]; int result; [op setAllowsMultipleSelection: NO]; [op setCanChooseFiles: YES]; [op setCanChooseDirectories: NO]; result=[op runModalForTypes: nil]; if (result==NSOKButton && [op filename]) { [f_SignatureValue setStringValue: [op filename]]; } } -(NSView *) willShow { if (!top) { top=[[GSVbox alloc] init]; [top setDefaultMinYMargin: 4]; { NSButton *b; b_PostQuotedPrintable=b=[[NSButton alloc] init]; [b setTitle: _(@"Post articles as quoted-printable")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [top addView: b enablingYResizing: NO]; DESTROY(b); } [top addSeparator]; { NSTextField *f; NSPopUpButton *pb; NSButton *b; GSHbox *hb; hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Source:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); f_SignatureValue=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hb addView: f enablingXResizing: YES]; DESTROY(f); b=[[NSButton alloc] init]; [b setTitle: _(@"Browse...")]; [b setTarget: self]; [b setAction: @selector(_signatureBrowse:)]; [b sizeToFit]; [hb addView: b enablingXResizing: NO]; DESTROY(b); [top addView: hb enablingYResizing: NO]; DESTROY(hb); hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Signature type:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); b_SignatureType=pb=[[NSPopUpButton alloc] init]; [pb setAutoenablesItems: NO]; [pb addItemWithTitle: _(@"Literal")]; [pb addItemWithTitle: _(@"Contents of file")]; [pb addItemWithTitle: _(@"Output from program")]; [pb sizeToFit]; // [pb setAutoresizingMask: NSViewWidthSizable]; [hb addView: pb enablingXResizing: YES]; DESTROY(pb); [top addView: hb enablingYResizing: NO]; DESTROY(hb); } [top addSeparator]; { NSTextField *f; GSHbox *hb; hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"From-address:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); f_FromAddress=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hb addView: f enablingXResizing: YES]; DESTROY(f); [top addView: hb enablingYResizing: NO]; DESTROY(hb); hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Name:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); f_PostingName=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hb addView: f enablingXResizing: YES]; DESTROY(f); [top addView: hb enablingYResizing: NO]; DESTROY(hb); } [self revert]; } return top; } -(void) dealloc { DESTROY(top); [super dealloc]; } @end LuserNET-0.4.2/Pref_ReadAhead.h100664 764 764 1146 7443502172 14246 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef Pref_ReadAhead_h #define Pref_ReadAhead_h #include "PrefBox.h" @class NSButton,NSTextField,GSVbox; @interface Pref_ReadAhead : NSObject { /* only top is directly retained */ GSVbox *top; NSButton *b_ReadAhead; NSButton *b_ReadAheadNextUnread, *b_ReadAheadNextThread, *b_ReadAheadNext, *b_ReadAheadPrevious; NSTextField *f_ReadAheadSizeLimit; } +(int) readAheadSizeLimit; +(BOOL) readAhead; +(BOOL) readAheadNextUnread; +(BOOL) readAheadNextThread; +(BOOL) readAheadNext; +(BOOL) readAheadPrevious; @end #endif LuserNET-0.4.2/Pref_ReadAhead.m100664 764 764 11235 10021217655 14306 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include "Pref_ReadAhead.h" #define ReadAheadKey @"ReadAhead" #define ReadAheadSizeLimitKey @"ReadAheadSizeLimit" #define ReadAheadNextKey @"ReadAheadNext" #define ReadAheadPreviousKey @"ReadAheadPrevious" #define ReadAheadNextThreadKey @"ReadAheadNextThread" #define ReadAheadNextUnreadKey @"ReadAheadNextUnread" static NSUserDefaults *sd; @implementation Pref_ReadAhead +(void) initialize { if (!sd) sd=[NSUserDefaults standardUserDefaults]; } +(int) readAheadSizeLimit { if ([sd objectForKey: ReadAheadSizeLimitKey]) return [sd integerForKey: ReadAheadSizeLimitKey]; else return 16*1024; } /* The underscores in the names are ugly, but gcc 2.95.3 doesn't accept eg. 'RA_GET_BOOL()'. */ #define readAhead_ readAhead #define ReadAhead_Key ReadAheadKey #define b_ReadAhead_ b_ReadAhead #define RA_GET_BOOL(x) \ +(BOOL) readAhead##x \ { \ if (![sd objectForKey: ReadAhead##x##Key] || \ [sd boolForKey: ReadAhead##x##Key]) \ return YES; \ else \ return NO; \ } RA_GET_BOOL(_) RA_GET_BOOL(NextUnread) RA_GET_BOOL(NextThread) RA_GET_BOOL(Next) RA_GET_BOOL(Previous) #undef RA_GET_BOOL -(void) save { NSUserDefaults *sd=[NSUserDefaults standardUserDefaults]; BOOL b; if (!top) return; #define SAVE_BOOL(x) \ b=[b_ReadAhead##x state]?YES:NO; \ [sd setBool: b forKey: ReadAhead##x##Key]; SAVE_BOOL(_) SAVE_BOOL(NextUnread) SAVE_BOOL(NextThread) SAVE_BOOL(Next) SAVE_BOOL(Previous) #undef SAVE_BOOL [sd setInteger: [f_ReadAheadSizeLimit intValue] forKey: ReadAheadSizeLimitKey]; } -(void) revert { #define REVERT_BOOL(x) \ if ([Pref_ReadAhead readAhead##x]) \ [b_ReadAhead##x setState: 1]; \ else \ [b_ReadAhead##x setState: 0]; REVERT_BOOL(_) REVERT_BOOL(NextUnread) REVERT_BOOL(NextThread) REVERT_BOOL(Next) REVERT_BOOL(Previous) #undef REVERT_BOOL [f_ReadAheadSizeLimit setIntValue: [Pref_ReadAhead readAheadSizeLimit]]; } -(NSString *) name { return _(@"Read-ahead"); } -(void) setupButton: (NSButton *)b { [b setTitle: _(@"Read-\nahead")]; [b sizeToFit]; } -(void) willHide { } -(NSView *) willShow { if (!top) { top=[[GSVbox alloc] init]; [top setDefaultMinYMargin: 4]; { NSTextField *f; GSHbox *hb; hb=[[GSHbox alloc] init]; [hb setDefaultMinXMargin: 4]; [hb setAutoresizingMask: NSViewWidthSizable]; f=[[NSTextField alloc] init]; [f setStringValue: _(@"Read-ahead downloads messages smaller than:")]; [f setEditable: NO]; [f setDrawsBackground: NO]; [f setBordered: NO]; [f setBezeled: NO]; [f setSelectable: NO]; [f sizeToFit]; [f setAutoresizingMask: 0]; [hb addView: f enablingXResizing: NO]; DESTROY(f); f_ReadAheadSizeLimit=f=[[NSTextField alloc] init]; [f setAutoresizingMask: NSViewWidthSizable]; [f sizeToFit]; [hb addView: f enablingXResizing: YES]; DESTROY(f); [top addView: hb enablingYResizing: NO]; DESTROY(hb); } { NSButton *b; NSBox *box; GSVbox *vb; box=[[NSBox alloc] init]; [box setTitle: @"Read ahead what?"]; vb=[[GSVbox alloc] init]; [vb setDefaultMinYMargin: 4]; b_ReadAheadNextThread=b=[[NSButton alloc] init]; [b setTitle: _(@"Next unread past the current thread.")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [vb addView: b enablingYResizing: NO]; DESTROY(b); b_ReadAheadNextUnread=b=[[NSButton alloc] init]; [b setTitle: _(@"Next unread.")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [vb addView: b enablingYResizing: NO]; DESTROY(b); b_ReadAheadPrevious=b=[[NSButton alloc] init]; [b setTitle: _(@"Previous.")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [vb addView: b enablingYResizing: NO]; DESTROY(b); b_ReadAheadNext=b=[[NSButton alloc] init]; [b setTitle: _(@"Next.")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [vb addView: b enablingYResizing: NO]; DESTROY(b); [vb sizeToFit]; [box setContentView: vb]; [box sizeToFit]; DESTROY(vb); [top addView: box enablingYResizing: NO]; DESTROY(box); b_ReadAhead_=b=[[NSButton alloc] init]; [b setTitle: _(@"Read ahead (ie. start downloading related messages\nwhen a message is selected).")]; [b setButtonType: NSSwitchButton]; [b sizeToFit]; [top addView: b enablingYResizing: NO]; DESTROY(b); } [self revert]; } return top; } -(void) dealloc { DESTROY(top); [super dealloc]; } @end LuserNET-0.4.2/Pref_Sources.h100664 764 764 562 7442753735 14070 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef Pref_Sources_h #define Pref_Sources_h @class MsgDB,GSHbox,NSTableView,NSTableColumn; @interface Pref_Sources : NSObject { MsgDB *mdb; NSArray *sources; GSHbox *top; NSTableView *source_list; NSTableColumn *c_num,*c_name,*c_type; } - initWithMsgDB: (MsgDB *)mdb; @end #endif LuserNET-0.4.2/Pref_Sources.m100664 764 764 12212 10021217655 14127 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include #include #include "PrefBox.h" #include "Pref_Sources.h" #include "MsgDB.h" #include "GUISource.h" #include "NNTPSource.h" @implementation Pref_Sources -(void) addSource: (id)sender { /* TODO: support different kinds of sources. need at least two kinds first */ [mdb addSourceOfType: [NNTPSource class]]; } -(void) removeSource: (id)sender { /* TODO: confirm with user first? */ if ([source_list selectedRow]!=-1) [mdb removeSource: [sources objectAtIndex: [source_list selectedRow]]]; } -(void) sourceProperties: (id)sender { if ([source_list selectedRow]!=-1) [[sources objectAtIndex: [source_list selectedRow]] displayPropertiesWindow]; } -(int) numberOfRowsInTableView: (NSTableView *)tv { return [sources count]; } -(id) tableView: (NSTableView *)tv objectValueForTableColumn: (NSTableColumn *)tc row: (int)row { id s; s=[sources objectAtIndex: row]; if (tc==c_num) { return [NSNumber numberWithInt: row]; } else if (tc==c_name) { return [s sourceName]; } else if (tc==c_type) { return [s sourceType]; } else return @"Unknown column"; } -(void) _getSources { DESTROY(sources); sources=[[mdb sources] retain]; [source_list reloadData]; } -(void) _createViews { GSHbox *hbox; NSScrollView *sv; hbox=[[GSHbox alloc] init]; [hbox setDefaultMinXMargin: 4]; sv=[[NSScrollView alloc] init]; [sv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [sv setHasVerticalScroller: YES]; [sv setHasHorizontalScroller: NO]; [sv setBorderType: NSBezelBorder]; c_num=[[NSTableColumn alloc] initWithIdentifier: @"Id"]; [[c_num headerCell] setStringValue: _(@"Id")]; [c_num setEditable: NO]; [c_num setResizable: YES]; [c_num setWidth: 32]; c_name=[[NSTableColumn alloc] initWithIdentifier: @"Name"]; [[c_name headerCell] setStringValue: _(@"Name")]; [c_name setEditable: NO]; [c_name setResizable: YES]; [c_name setWidth: 128]; c_type=[[NSTableColumn alloc] initWithIdentifier: @"Type"]; [[c_type headerCell] setStringValue: _(@"Type")]; [c_type setEditable: NO]; [c_type setResizable: YES]; [c_type setWidth: 64]; source_list=[[NSTableView alloc] initWithFrame: [[sv contentView] frame]]; [source_list setAllowsColumnReordering: YES]; [source_list setAllowsColumnResizing: YES]; [source_list setAllowsMultipleSelection: NO]; [source_list setAllowsColumnSelection: NO]; [source_list addTableColumn: c_num]; [source_list addTableColumn: c_name]; [source_list addTableColumn: c_type]; [source_list setDataSource: self]; [source_list setDelegate: self]; [source_list setDoubleAction: @selector(sourceProperties:)]; [sv setDocumentView: source_list]; [hbox addView: sv enablingXResizing: YES]; [sv release]; { GSVbox *vb; NSButton *b; vb=[[GSVbox alloc] init]; [vb setDefaultMinYMargin: 4]; [vb setAutoresizingMask: NSViewMinYMargin]; b=[[NSButton alloc] init]; [b setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [b setTitle: _(@"Remove")]; [b setTarget: self]; [b setAction: @selector(removeSource:)]; [b sizeToFit]; [vb addView: b]; [b release]; b=[[NSButton alloc] init]; [b setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [b setTitle: _(@"Add")]; [b setTarget: self]; [b setAction: @selector(addSource:)]; [b sizeToFit]; [vb addView: b]; [b release]; b=[[NSButton alloc] init]; [b setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [b setTitle: _(@"Properties...")]; [b setTarget: self]; [b setAction: @selector(sourceProperties:)]; [b sizeToFit]; [vb addView: b]; [b release]; [hbox addView: vb enablingXResizing: NO]; [vb release]; } [hbox setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; top=hbox; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_getSources) name: MsgDB_SourceAddNotification object: mdb]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_getSources) name: MsgDB_SourceRemoveNotification object: mdb]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_getSources) name: MsgDB_SourceChangeNotification object: mdb]; [self _getSources]; } - initWithMsgDB: (MsgDB *)m { self=[super init]; ASSIGN(mdb,m); return self; } -(void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver: self]; DESTROY(sources); DESTROY(mdb); DESTROY(source_list); DESTROY(c_num); DESTROY(c_name); DESTROY(c_type); DESTROY(top); [super dealloc]; } -(void) setupButton: (NSButton *)b { [b setTitle: _(@"Message\nsources")]; [b sizeToFit]; } -(void) willHide { } -(NSView *) willShow { if (!top) [self _createViews]; return top; } -(NSString *) name { return _(@"Message sources"); } -(void) revert { [self _getSources]; } -(void) save { } @end LuserNET-0.4.2/PreferencesWindowController.h100664 764 764 742 7442753735 17166 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef PreferencesWindowController_h #define PreferencesWindowController_h #include #include "PrefBox.h" @class GSHbox,NSBox; @interface PreferencesWindowController : NSWindowController { GSHbox *button_box; NSBox *pref_box; NSObject *current; NSMutableArray *pref_boxes; NSMutableArray *pref_buttons; } -(void) addPrefBox: (NSObject *)pb; @end #endif LuserNET-0.4.2/README100664 764 764 16526 10021217655 12246 0ustar alexalex This is a news reader. It needs a name (at least a better one :). Copyright 2001-2002 Alexander Malmberg It's released under the GNU General Public License. The icon (News.app.tiff) comes from a collection of icons drawn by Andrew Lindesay. This is an early, but functional, version. At this stage it should be fully usable as an online news reader. Comments, suggestions, bug reports etc. are appreciated. Send them to . There's a basic LuserNET site at: http://w1.423.telia.com/~u42308495/alex/LuserNET/LuserNET.html -- Installing -- Requires gnustep-make, gnustep-base, gnustep-gui and Pantomime. I've used late (even future :) cvs versions, YMMV. To build: make If you want to install it to some standard location: make install -- Setting up -- To change settings, select Info->Preferences in the menu. The message database is by default stored in ~/GNUstep/Library/LuserNET/ (or equivalent). It will be created the first time you run the program. To get some messages, you'll need to add some sources. The sources tab has a list of message-sources. Currently only NNTP sources are supported. Click Add to add one. Double-click on it (or select it and click properties) to bring up the properties panel for that source. The NNTP properties panel lets you enter the host for this source and the port (leave it blank to use the standard nntp port, 119). You can also add and remove groups that are downloaded through this source. To add a source, enter it's name, click Query, look in the log window to get the valid range of messages, and enter a suitable value for 'Last message'. 0 will download _all_ messages your news server has for that group, which might take a _very_ long while. To actually download any new messages that have arrived, press alt-u (or select update in the menu). Note that you need to click OK or Query in the NNTP properties panel to update the internal values for host and port. The read-ahead tab lets you change the read-ahead settings: which messages are downloaded, and how large they may be. If you're on a slow link, you might want to disable some of these settings or reduce the maximum size that's downloaded. The message viewing tab lets you change settings related to how you view messages. The two fonts (primary and alternate, defaulting to the normal user font and the normal fixed pitch font, respectively) used when viewing messages can be changed here. Coloring lines based on quoting depth helps readability (IMO), but can be slow on large messages). Intelligent scroll means that when you hit ' ' to scroll a page down in the current message, quoted sections and signatures will be automatically skipped. This helps a lot when people quote too long portions of other's messages since it saves you from having to search for the new parts manually. The size limit for automatically downloading a message when you click on it can also be changed here. If you're on a slow link you might want to decrease it. -- Using -- The log window contains a log of interesting stuff that has happened. You can probably ignore it most of the time as long as things are running smoothly. The folder list contains a list of (non-empty) folders. Double-click on a folder to open the folder window for that folder. A folder window has a list of messages in that folder and a view of the currently selected message. Keys: up/down/ page-up/ page-down/ home/end Move around in the list space Scroll down in current message, or move to next unread message if you're at the end of the current message. If intelligent message scrolling is enabled this will scroll intelligently. n Next unread message s/x Move up/down a small amount in the current message P Move to this message's parent b Move to the next branch B Mark this branch as read and move to the next branch t Move to the next thread T Mark this thread as read and move to the next thread m Toggle message read/unread A Mark all messages in the current folder as read The references header will be used to thread the messages. If an unknown message is referenced, it'll be created and displayed with it's Message-ID as its subject (due to out-of-order delivery it might appear with real headers if you update again). By default the folder window is threaded. You can change this in 'Folder->Sort by'. Current options are threaded (new threads added at the bottom), reverse-threaded (new threads added at the top), arrival order (the order in which the messages arrived to the program, not necessarily the order in which they were sent), and reverse arrival order. Thread based movement will currently only work in threaded folders (not reverse-threaded). (The order setting will be remembered for each folder.) Normal messages will be displayed as plain text (maybe colored based on quoting depth) prefixed by some interesting headers for that message. If a message was a MIME message it might consist of multiple parts. In that case, each part will have a short header that tells you what it is. Generally, deep blue text is information, and red text is clickable ('link':s, for lack of better words). Different content-types are handled as follows: text/* parts (usually plain text) Displayed 'normally' if it could be decoded. If it couldn't be decoded (most likely due to an unknown character set), it'll be treated like raw data but with the addition of a 'Try to show as text' link. If you click this, the message will be treated as ASCII text. If the message was encoded with a character set that's similar to ASCII (the first 128 characters), this will probably display most of the message correctly. multipart/alternative The last part will be displayed by default. The header will have a list of content-types for all parts; clicking on one will display that part instead (thus, if you get an html message with a plain text part as an alternative, you can view that part instead). multipart/* All parts will be displayed. everything else The header will tell you the content-type, and information like the size and filename, if available. There'll be a save option you can use to save the raw data of the part (which is what you'll usually want to do with eg. attachments). If a message has not been downloaded yet, you'll get a status message instead, sometimes with links. (For example, if a message was larger than the automatic-download size limit, or the size is unknown, you can click the link to download it anyway.) -- Notes -- Current versions of GNUstep are very slow when it comes to arranging text. Thus, for large messages it might take a few seconds after it has been downloaded for it to appear. If you get an error when downloading a message it most likely means that the server doesn't have that message. It might appear later. Threading will only be cached in memory. Threading a large folder happens the first time you open it and might take a little while. It is possible to remove sources and groups, but messages that have already been downloaded can't be deleted (currently). If you want to start from scratch again just delete ~/GNUstep/Library/LuserNET/metahead.txt . The text-based version is no longer included. If you're interested in it, tell me. -- Planned features -- Support for uuencoded binary files in messages. (MIME is better, but this seems common.) Posting support. Viewing image parts of messages inline. LuserNET-0.4.2/PreferencesWindowController.m100664 764 764 11765 10021217655 17241 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "autokeyviewchain.h" #include "PreferencesWindowController.h" #include "PrefBox.h" @implementation PreferencesWindowController -(void) save: (id)sender { [pref_boxes makeObjectsPerformSelector: @selector(save)]; } -(void) revert: (id)sender { [pref_boxes makeObjectsPerformSelector: @selector(revert)]; } - init { NSWindow *win; win=[[NSPanel alloc] initWithContentRect: NSMakeRect(100,100,380,340) styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask backing: NSBackingStoreRetained defer: YES]; if (!(self=[super initWithWindow: win])) return nil; { GSVbox *vbox; vbox=[[GSVbox alloc] init]; [vbox setBorder: 4]; [vbox setDefaultMinYMargin: 4]; { NSButton *b; GSHbox *hbox; hbox=[[GSHbox alloc] init]; [hbox setDefaultMinXMargin: 4]; [hbox setAutoresizingMask: NSViewMinXMargin]; b=[[NSButton alloc] init]; [b setTitle: _(@"Revert")]; [b setTarget: self]; [b setAction: @selector(revert:)]; [b sizeToFit]; [hbox addView: b]; [b release]; b=[[NSButton alloc] init]; [b setTitle: _(@"Save")]; [b setKeyEquivalent: @"\r"]; [b setTarget: self]; [b setAction: @selector(save:)]; [b sizeToFit]; [hbox addView: b]; [b release]; [vbox addView: hbox enablingYResizing: NO]; [hbox release]; } { pref_box=[[NSBox alloc] initWithFrame: NSMakeRect(0,0,1,1)]; [pref_box setTitle: @""]; [pref_box setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [pref_box setAutoresizesSubviews: YES]; [vbox addView: pref_box enablingYResizing: YES]; } { NSScrollView *sv; NSSize s; button_box=[[GSHbox alloc] init]; s=[NSScrollView frameSizeForContentSize: NSMakeSize(1,68) hasHorizontalScroller: YES hasVerticalScroller: YES borderType: NSNoBorder]; /* TODO? */ sv=[[NSScrollView alloc] initWithFrame: NSMakeRect(0,0,1,s.height)]; [sv setDocumentView: button_box]; [sv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; [sv setHasHorizontalScroller: YES]; [sv setBorderType: NSBezelBorder]; [vbox addView: sv enablingYResizing: NO]; DESTROY(sv); } [win setContentView: vbox]; [vbox release]; } [win setDelegate: self]; [win setTitle: _(@"Preferences")]; [win setFrameUsingName: @"Preferences"]; [win setFrameAutosaveName: @"Preferences"]; [win autoSetupKeyViewChain]; [win release]; pref_boxes=[[NSMutableArray alloc] init]; pref_buttons=[[NSMutableArray alloc] init]; return self; } -(void) dealloc { if (current) { [current willHide]; current=nil; } DESTROY(pref_boxes); DESTROY(pref_buttons); DESTROY(button_box); [super dealloc]; } -(void) _displayBox: (NSObject *)pb { int idx=[pref_boxes indexOfObjectIdenticalTo: pb]; if (idx==NSNotFound) return; if (current==pb) return; if (current) { [[pref_buttons objectAtIndex: [pref_boxes indexOfObjectIdenticalTo: current]] setState: 0]; [current willHide]; current=nil; } [[pref_buttons objectAtIndex: idx] setState: 1]; [pref_box setTitle: [pb name]]; [pref_box setContentView: [pb willShow]]; current=pb; [[self window] autoSetupKeyViewChain]; } -(void) _displayBoxButton: (id)sender { int idx=[pref_buttons indexOfObjectIdenticalTo: sender]; if (idx==NSNotFound) return; if ([pref_boxes objectAtIndex: idx]==current) { [sender setState: 1]; return; } [self _displayBox: [pref_boxes objectAtIndex: idx]]; } -(void) addPrefBox: (NSObject *)pb { NSButton *b=[[NSButton alloc] init]; [pref_boxes addObject: pb]; [pref_buttons addObject: b]; [pb setupButton: b]; if ([b frame].size.height<=64) [b setFrame: NSMakeRect(0,0,[b frame].size.width,64)]; [b setTarget: self]; [b setAction: @selector(_displayBoxButton:)]; [b setButtonType: NSPushOnPushOffButton]; [button_box addView: b]; [button_box sizeToFit]; if (!current) [self _displayBox: pb]; else [[self window] autoSetupKeyViewChain]; } /* well, it works */ -(BOOL) respondsToSelector: (SEL)s { if ([super respondsToSelector: s]) return YES; if (current) return [current respondsToSelector: s]; return NO; } -(void) forwardInvocation: (NSInvocation *)i { if (current) if ([current respondsToSelector: [i selector]]) { [i invokeWithTarget: current]; return; } [super forwardInvocation: i]; } -(NSMethodSignature *) methodSignatureForSelector: (SEL)sel { NSMethodSignature *ms; ms=[super methodSignatureForSelector: sel]; if (ms) return ms; ms=[current methodSignatureForSelector: sel]; return ms; } @end LuserNET-0.4.2/VERSION100664 764 764 61 10021220133 12322 0ustar alexalex#define VERSION "0.4.2" /* VERSION = 0.4.2 # */ LuserNET-0.4.2/autokeyviewchain.h100664 764 764 454 7442773464 15051 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef autokeyviewchain_h #define autokeyviewchain_h @interface NSWindow (autokeyviewchain) -(void) autoSetupKeyViewChain; @end @interface NSView (autokeyviewchain) -(NSView *) autoSetupKeyViewChain: (NSView *) next; @end #endif LuserNET-0.4.2/autokeyviewchain.m100664 764 764 3045 10021217655 15073 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include "autokeyviewchain.h" @implementation NSWindow (autokeyviewchain) -(void) autoSetupKeyViewChain { NSView *v=[self contentView]; [self setInitialFirstResponder: [v autoSetupKeyViewChain: nil]]; /* { NSView *v=[self initialFirstResponder]; printf("window %p initial=%p\n",self,v); for (;v;v=[v nextKeyView]) { printf(" '%@'\n",v); } }*/ } @end /* just to get rid of warnings */ #include #include @implementation NSView (autokeyviewchain) -(NSView *) autoSetupKeyViewChain: (NSView *) next { if ([self acceptsFirstResponder]) { [self setNextKeyView: next]; next=self; } /* use NSScrollView to get rid of warning */ if ([self respondsToSelector: @selector(documentView)]) next=[[(NSScrollView *)self documentView] autoSetupKeyViewChain: next]; else if ([self respondsToSelector: @selector(contentView)]) next=[[(NSScrollView *)self contentView] autoSetupKeyViewChain: next]; return next; } @end #include @implementation GSTable (autokeyviewchain) -(NSView *) autoSetupKeyViewChain: (NSView *) next { int i,j; NSView *v; for (i=0;i<_numberOfRows;i++) { for (j=_numberOfColumns-1;j>=0;j--) { if (_jails[i*_numberOfColumns+j]) { v=[[(NSView *)_jails[i*_numberOfColumns+j] subviews] objectAtIndex: 0]; next=[v autoSetupKeyViewChain: next]; } } } return next; } @end LuserNET-0.4.2/main.h100664 764 764 1602 10021217655 12430 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #ifndef main_h #define main_h #include "MsgDB.h" @class NSApplication,NSMutableDictionary; @class FolderListController,LogWindowController,PreferencesWindowController; @class Message; @interface AppDelegate : NSObject { NSApplication *app; NSMutableDictionary *folder_windows; MsgDB *mdb; FolderListController *folder_list; LogWindowController *log_window; PreferencesWindowController *preferences_window; } -(void) openFolderWindow: (NSString *)folder_name; -(void) openMessageWindow: (msg_id_t)mid; -(void) composeArticle: (NSDictionary *)headers; -(void) composeFollowupToMessage: (Message *)msg; -(void) postArticleFrom: (id)sender : (const unsigned char *)data : (int)length; /* TODO */ @end @interface AppDelegate (prefs) -(void) openPreferences: (id)sender; @end extern AppDelegate *app_delegate; #endif LuserNET-0.4.2/main.m100664 764 764 25334 10021217655 12465 0ustar alexalex/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include #include #include #include #include "MsgDB.h" #include "LogWindowController.h" #include "FolderWindowController.h" #include "FolderListController.h" #include "ComposeWindowController.h" #include "main.h" #include "VERSION" AppDelegate *app_delegate; #define LOG_MESSAGES_ONLY #define LogWindowVisibleKey @"LogWindowVisible" #define FolderListVisibleKey @"FolderListVisible" #define FoldersVisibleKey @"FoldersVisible" #define LocationKey @"MessageDatabaseLocation" @interface NSMenu (im_lazy) -(id ) addItemWithTitle: (NSString *)s; -(id ) addItemWithTitle: (NSString *)s action: (SEL)sel; @end @implementation NSMenu (im_lazy) -(id ) addItemWithTitle: (NSString *)s { return [self addItemWithTitle: s action: NULL keyEquivalent: nil]; } -(id ) addItemWithTitle: (NSString *)s action: (SEL)sel { return [self addItemWithTitle: s action: sel keyEquivalent: nil]; } @end #include @implementation AppDelegate -(void) composeArticle: (NSDictionary *)headers; { [[[ComposeWindowController alloc] initWithHeaders: headers] showWindow: self]; } -(void) composeFollowupToMessage: (Message *)msg { [[[ComposeWindowController alloc] initWithFollowupToMessage: msg] showWindow: self]; } -(void) composeNewArticle: (id)sender { [self composeArticle: nil]; } -(void) postArticleFrom: (id)sender : (const unsigned char *)data : (int)length { /* TODO */ NSObject *src; // fprintf(stderr,"post article %@\n",sender); src=[[mdb sources] objectAtIndex: 0]; if (!src || ![src respondsToSelector: @selector(postArticle:length:sender:)]) { fprintf(stderr,"no source!\n"); NSBeep(); return; } [src postArticle: data length: length sender: sender]; } - initWithApp: (NSApplication *)a { CREATE_AUTORELEASE_POOL(arp); NSString *dir; if (!(self=[super init])) return nil; app=a; folder_windows=[[NSMutableDictionary alloc] init]; dir=[[NSUserDefaults standardUserDefaults] objectForKey: LocationKey]; if (!dir) { dir=[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES) objectAtIndex: 0]; dir=[[dir stringByAppendingPathComponent: @"LuserNET"] stringByStandardizingPath]; [[NSUserDefaults standardUserDefaults] setObject: dir forKey: LocationKey]; } mdb=[[MsgDB alloc] initWithDirectory: dir]; DESTROY(arp); return self; } -(void) applicationWillTerminate: (NSNotification *)n { [mdb syncToDisk]; [[NSNotificationCenter defaultCenter] removeObserver: self]; { NSUserDefaults *ud=[NSUserDefaults standardUserDefaults]; NSMutableArray *a; NSEnumerator *e; NSString *fn; [ud setBool: [[log_window window] isVisible] forKey: LogWindowVisibleKey]; if (folder_list) [ud setBool: [[folder_list window] isVisible] forKey: FolderListVisibleKey]; else [ud setBool: NO forKey: FolderListVisibleKey]; a=[[NSMutableArray alloc] init]; for (e=[folder_windows keyEnumerator];(fn=[e nextObject]);) { if ([[[folder_windows objectForKey: fn] window] isVisible]) [a addObject: fn]; } [ud setObject: a forKey: FoldersVisibleKey]; [a release]; } DESTROY(preferences_window); DESTROY(log_window); DESTROY(folder_list); DESTROY(folder_windows); DESTROY(mdb); } -(void) openFolderWindow: (NSString *)folder_name { FolderWindowController *fw; fw=[folder_windows objectForKey: folder_name]; if (!fw) { fw=[[FolderWindowController alloc] initWithMsgDB: mdb folder: folder_name]; if (!fw) return; /* TODO: bring up error panel? */ [folder_windows setObject: fw forKey: folder_name]; [fw release]; } [fw showWindow: self]; } -(void) openMessageWindow: (msg_id_t)mid { NSLog(@"TODO: openMessageWindow: %i\n",mid); } -(void) quit: (id)sender { [app terminate: sender]; } -(void) update: (id)sender { [mdb updateAll]; } -(void) listFolders: (id)sender { if (!folder_list) folder_list=[[FolderListController alloc] initWithMsgDB: mdb]; [folder_list showWindow: self]; } -(void) logWindow: (id)sender { [log_window showWindow: self]; } -(void) logMessage: (NSNotification *)n { #ifdef LOG_MESSAGES_ONLY [log_window addLogString: [[n userInfo] objectForKey: @"Message"]]; #else [log_window addLogString: [NSString stringWithFormat: @"%@: %@ %@ %@",n,[n name],[n object],[n userInfo]]]; #endif } -(void) applicationWillFinishLaunching: (NSNotification *)n { NSMenu *menu,*m,*m2; menu=[[NSMenu alloc] init]; /* 'Info' menu */ m=[[NSMenu alloc] init]; [m addItemWithTitle: _(@"Preferences...") action: @selector(openPreferences:)]; [m addItemWithTitle: _(@"Info...") action: @selector(orderFrontStandardInfoPanel:)]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Info")]]; [m release]; /* 'Folder list' menu */ m=[[NSMenu alloc] init]; [m addItemWithTitle: _(@"Open list") action: @selector(listFolders:) keyEquivalent: @"f"]; [m addItemWithTitle: _(@"Open folder") action: @selector(openFolder:)]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Folder list")]]; [m release]; /* 'Folder' menu */ m=[[NSMenu alloc] init]; /* Folder->Move to */ m2=[[NSMenu alloc] init]; [m2 addItemWithTitle: _(@"Parent") action: @selector(moveToParent:) keyEquivalent: @"P"]; [m2 addItemWithTitle: _(@"Next unread") action: @selector(moveToNextUnread:) keyEquivalent: @"n"]; [m2 addItemWithTitle: _(@"Scroll/next") action: @selector(scrollNextUnread:) keyEquivalent: @" "]; [m2 addItemWithTitle: _(@"Next branch") action: @selector(skipBranch:) keyEquivalent: @"b"]; [m2 addItemWithTitle: _(@"Next thread") action: @selector(skipBranch:) keyEquivalent: @"t"]; [m setSubmenu: m2 forItem: [m addItemWithTitle: _(@"Move to")]]; [m2 release]; /* Folder->Mark read */ m2=[[NSMenu alloc] init]; [m2 addItemWithTitle: _(@"Branch") action: @selector(skipBranchMark:) keyEquivalent: @"B"]; [m2 addItemWithTitle: _(@"Thread") action: @selector(skipBranchMark:) keyEquivalent: @"T"]; [m2 addItemWithTitle: _(@"All") action: @selector(markAll:) keyEquivalent: @"A"]; [m setSubmenu: m2 forItem: [m addItemWithTitle: _(@"Mark read")]]; [m2 release]; /* Folder->Sort by */ m2=[[NSMenu alloc] init]; [m2 addItemWithTitle: _(@"Thread") action: @selector(folderSortThread)]; [m2 addItemWithTitle: _(@"Reverse thread") action: @selector(folderSortReverseThread)]; /* TODO: these are currently unusably slow [m2 addItemWithTitle: _(@"Subject") action: @selector(folderSortSubject)]; [m2 addItemWithTitle: _(@"Reverse subject") action: @selector(folderSortReverseSubject)]; [m2 addItemWithTitle: _(@"From") action: @selector(folderSortFrom)]; [m2 addItemWithTitle: _(@"Reverse from") action: @selector(folderSortReverseFrom)]; [m2 addItemWithTitle: _(@"Date") action: @selector(folderSortDate)]; [m2 addItemWithTitle: _(@"Reverse date") action: @selector(folderSortReverseDate)];*/ [m2 addItemWithTitle: _(@"Arrival") action: @selector(folderSortOrder)]; [m2 addItemWithTitle: _(@"Reverse arrival") action: @selector(folderSortReverseOrder)]; [m setSubmenu: m2 forItem: [m addItemWithTitle: _(@"Sort by")]]; [m2 release]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Folder")]]; [m release]; /* 'Message' menu */ m=[[NSMenu alloc] init]; /* [m addItemWithTitle: _(@"Open") action: @selector(openMessage:)];*/ [m addItemWithTitle: _(@"Toggle read/unread") action: @selector(messageToggleRead:) keyEquivalent: @"m"]; [m addItemWithTitle: _(@"Switch font") action: @selector(switchFont) keyEquivalent: @"F"]; [m addItemWithTitle: _(@"Show source") action: @selector(showSource)]; [m addItemWithTitle: _(@"Download") action: @selector(messageDownload)]; [m addItemWithTitle: _(@"Save...") action: @selector(messageSave) keyEquivalent: @"s"]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Message")]]; [m release]; /* 'Compose' menu */ m=[[NSMenu alloc] init]; [m addItemWithTitle: _(@"New article") action: @selector(composeNewArticle:)]; [m addItemWithTitle: _(@"Followup to group") action: @selector(composeFollowup:) keyEquivalent: @"r"]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Compose")]]; [m release]; /* 'Edit' menu */ m=[[NSMenu alloc] init]; [m addItemWithTitle: _(@"Copy") action: @selector(copy:) keyEquivalent: @"c"]; [m addItemWithTitle: _(@"Cut") action: @selector(cut:) keyEquivalent: @"x"]; [m addItemWithTitle: _(@"Paste") action: @selector(paste:) keyEquivalent: @"v"]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Edit")]]; [m release]; /* Main menu entries */ [menu addItemWithTitle: _(@"Update") action: @selector(update:) keyEquivalent: @"u"]; [menu addItemWithTitle: _(@"Log window") action: @selector(logWindow:) keyEquivalent: @"l"]; m=[[NSMenu alloc] init]; [m addItemWithTitle: _(@"Close") action: @selector(performClose:) keyEquivalent: @"w"]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Windows")]]; [app setWindowsMenu: m]; [m release]; m=[[NSMenu alloc] init]; [menu setSubmenu: m forItem: [menu addItemWithTitle: _(@"Services")]]; [app setServicesMenu: m]; [m release]; [menu addItemWithTitle: _(@"Hide") action: @selector(hide:) keyEquivalent: @"h"]; [menu addItemWithTitle: _(@"Quit") action: @selector(quit:) keyEquivalent: @"q"]; [app setMainMenu: menu]; [menu setTitle: @"LuserNET.app"]; [menu release]; log_window=[[LogWindowController alloc] init]; [log_window addLogString: [NSString stringWithFormat: _(@"Welcome to LuserNET.app v%s"),VERSION]]; [log_window addLogString: _(@"alt-f brings up the folder lists; alt-u checks for new messages")]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(logMessage:) #ifdef LOG_MESSAGES_ONLY name: MsgDB_LogMessageNotification #else name: nil #endif object: mdb]; } -(void) applicationDidFinishLaunching: (NSNotification *)n { NSUserDefaults *ud=[NSUserDefaults standardUserDefaults]; NSArray *a; if ([ud boolForKey: LogWindowVisibleKey]) [log_window showWindow: self]; if ([ud boolForKey: FolderListVisibleKey]) [self listFolders: self]; a=[ud arrayForKey: FoldersVisibleKey]; if (a) { int i,c=[a count]; for (i=0;i */ #include #include "main.h" #include "PrefBox.h" #include "Pref_Sources.h" #include "Pref_MessageViewing.h" #include "Pref_ReadAhead.h" #include "Pref_Posting.h" #include "PreferencesWindowController.h" @implementation AppDelegate (prefs) -(void) openPreferences: (id)sender { if (!preferences_window) { NSObject *pb; preferences_window=[[PreferencesWindowController alloc] init]; pb=[[Pref_Sources alloc] initWithMsgDB: mdb]; [preferences_window addPrefBox: pb]; DESTROY(pb); pb=[[Pref_MessageViewing alloc] init]; [preferences_window addPrefBox: pb]; DESTROY(pb); pb=[[Pref_ReadAhead alloc] init]; [preferences_window addPrefBox: pb]; DESTROY(pb); pb=[[Pref_Posting alloc] init]; [preferences_window addPrefBox: pb]; DESTROY(pb); } [preferences_window showWindow: self]; } @end LuserNET-0.4.2/English.lproj/ 40775 764 764 0 10021220141 13735 5ustar alexalexLuserNET-0.4.2/English.lproj/Localizable.strings100664 764 764 31205 10021217655 17727 0ustar alexalex/* This is basically a dummy; everything is indentity-mapped. */ /*** English.lproj/Localizable.strings updated by make_strings 2002-10-03 14:07:01 +0200 add comments above this one ***/ /*** Keys found in multiple places ***/ /* File: NNTPSourceGUI.m:228 */ /* File: Pref_Sources.m:149 */ "Add" = "Add"; /* File: NNTPSourceGUI.m:237 */ /* File: Pref_Sources.m:140 */ "Remove" = "Remove"; /* File: FolderListController.m:69 */ /* File: main.m:235 */ "Folder list" = "Folder list"; /* File: FolderListController.m:72 */ /* File: Pref_Sources.m:103 */ "Name" = "Name"; /* File: MessageViewController.m:288 */ /* File: MessageViewController.m:391 */ /* File: main.m:320 */ "Save..." = "Save..."; /* File: MessageViewController.m:696 */ /* File: main.m:318 */ "Download" = "Download"; /* File: ComposeWindowController.m:260 */ /* File: MessageViewController.m:550 */ "From:" = "From:"; /* File: ComposeWindowController.m:208 */ /* File: MessageViewController.m:548 */ "Subject:" = "Subject:"; /*** Unmatched/untranslated keys ***/ /* File: main.m:223 */ "Info..." = "Info..."; /*** Strings from ComposeWindowController.m ***/ /* File: ComposeWindowController.m:234 */ "Newsgroups (separate with commas):" = "Newsgroups (separate with commas):"; /* File: ComposeWindowController.m:279 */ "Post" = "Post"; /*** Strings from FolderListController.m ***/ /* File: FolderListController.m:78 */ "Messages" = "Messages"; /*** Strings from FolderWindowController.m ***/ /* File: FolderWindowController.m:675 */ "Date" = "Date"; /* File: FolderWindowController.m:669 */ "From" = "From"; /* File: FolderWindowController.m:657 */ "Subject" = "Subject"; /*** Strings from LogWindowController.m ***/ /* File: LogWindowController.m:34 */ "Log" = "Log"; /*** Strings from MessageViewController.m ***/ /* File: MessageViewController.m:692 */ " unknown" = " unknown"; /* File: MessageViewController.m:226 */ "Alternatives:" = "Alternatives:"; /* File: MessageViewController.m:556 */ "Content-type:" = "Content-type:"; /* File: MessageViewController.m:224 */ "Currently shown:" = "Currently shown:"; /* File: MessageViewController.m:710 */ "Data cannot be downloaded for %s since it has no source." = "Data cannot be downloaded for %s since it has no source."; /* File: MessageViewController.m:705 */ "Data for %s is currently being downloaded." = "Data for %s is currently being downloaded."; /* File: MessageViewController.m:700 */ "Data for %s is unavailable due to an error." = "Data for %s is unavailable due to an error."; /* File: MessageViewController.m:554 */ "Date:" = "Date:"; /* File: MessageViewController.m:321 */ "Description:" = "Description:"; /* File: MessageViewController.m:240 */ "Displaying multipart with %i parts: %@\n" = "Displaying multipart with %i parts: %@\n"; /* File: MessageViewController.m:326 */ "Filename:" = "Filename:"; /* File: MessageViewController.m:399 */ "Hide raw data" = "Hide raw data"; /* File: MessageViewController.m:414 */ "Hide text" = "Hide text"; /* File: MessageViewController.m:552 */ "Newsgroups:" = "Newsgroups:"; /* File: MessageViewController.m:683 */ "No data has been downloaded for %s." = "No data has been downloaded for %s."; /* File: MessageViewController.m:540 */ "No icon for part.\n" = "No icon for part.\n"; /* File: MessageViewController.m:589 */ "Raw source:\n\n" = "Raw source:\n\n"; /* File: MessageViewController.m:828 */ "Save data" = "Save data"; /* File: MessageViewController.m:1110 */ "Save message" = "Save message"; /* File: MessageViewController.m:406 */ "Show raw data" = "Show raw data"; /* File: MessageViewController.m:688 */ /* File: MessageViewController.m:387 */ /* File: MessageViewController.m:291 */ "Size:" = "Size:"; /* File: MessageViewController.m:418 */ "Try to show as text" = "Try to show as text"; /* File: MessageViewController.m:382 */ "Type:" = "Type:"; /* File: MessageViewController.m:286 */ "UUEncoded file:" = "UUEncoded file:"; /* File: MessageViewController.m:372 */ "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n" = "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n"; /* File: MessageViewController.m:568 */ "Unable to render part of class '%@'. Please report this as a bug.\n" = "Unable to render part of class '%@'. Please report this as a bug.\n"; /* File: MessageViewController.m:678 */ "Unknown status %i for %s." = "Unknown status %i for %s."; /* File: MessageViewController.m:246 */ "\nPart:" = "\nPart:"; /* File: MessageViewController.m:718 */ "\n\nYou can set a source for this message by clicking on one of the sources:\n\n" = "\n\nYou can set a source for this message by clicking on one of the sources:\n\n"; /*** Strings from NNTPServer.m ***/ /* File: NNTPServer.m:563 */ "Can't open connection: %s" = "Can't open connection: %s"; /* File: NNTPServer.m:1155 */ "Closing connection: %i %s" = "Closing connection: %i %s"; /* File: NNTPServer.m:542 */ "Connecting to %i.%i.%i.%i:%i..." = "Connecting to %i.%i.%i.%i:%i..."; /* File: NNTPServer.m:1162 */ "New connection (%i total): %i %s" = "New connection (%i total): %i %s"; /* File: NNTPServer.m:476 */ "Resolved '%s' to %i.%i.%i.%i" = "Resolved '%s' to %i.%i.%i.%i"; /* File: NNTPServer.m:1179 */ /* File: NNTPServer.m:1174 */ "Unexpected response when connecting: %i %s" = "Unexpected response when connecting: %i %s"; /* File: NNTPServer.m:554 */ "can't create socket: %s" = "can't create socket: %s"; /* File: NNTPServer.m:578 */ "fcntl failed to set non-blocking mode: %s" = "fcntl failed to set non-blocking mode: %s"; /* File: NNTPServer.m:469 */ "lookup of %s failed: %s" = "lookup of %s failed: %s"; /* File: NNTPServer.m:513 */ /* File: NNTPServer.m:500 */ "warning: can't find protocol, assuming 0" = "warning: can't find protocol, assuming 0"; /* File: NNTPServer.m:488 */ "warning: can't find service 'nntp', assuming port 119" = "warning: can't find service 'nntp', assuming port 119"; /*** Strings from NNTPSource.m ***/ /* File: NNTPSource.m:221 */ "%i new messages in '%s', retrieving headers..." = "%i new messages in '%s', retrieving headers..."; /* File: NNTPSource.m:165 */ "Downloaded %ikb for '%@'" = "Downloaded %ikb for '%@'"; /* File: NNTPSource.m:144 */ "Failed to connect: %@" = "Failed to connect: %@"; /* File: NNTPSource.m:325 */ "Got headers in '%s': %5i/%5i (%5i) ..." = "Got headers in '%s': %5i/%5i (%5i) ..."; /* File: NNTPSource.m:338 */ "Got headers in '%s': %5i/%5i (%5i) Done." = "Got headers in '%s': %5i/%5i (%5i) Done."; /* File: NNTPSource.m:184 */ "Group '%s' contains message %i - %i." = "Group '%s' contains message %i - %i."; /* File: NNTPSource.m:194 */ "Group '%s' doesn't exist." = "Group '%s' doesn't exist."; /* File: NNTPSource.m:209 */ "No new messages in '%s'." = "No new messages in '%s'."; /*** Strings from NNTPSourceGUI.m ***/ /* File: NNTPSourceGUI.m:101 */ "" = ""; /* File: NNTPSourceGUI.m:385 */ "" = ""; /* File: NNTPSourceGUI.m:148 */ "Done" = "Done"; /* File: NNTPSourceGUI.m:272 */ "Group" = "Group"; /* File: NNTPSourceGUI.m:204 */ "Group name:" = "Group name:"; /* File: NNTPSourceGUI.m:331 */ "Host:" = "Host:"; /* File: NNTPSourceGUI.m:185 */ "Last message (use query):" = "Last message (use query):"; /* File: NNTPSourceGUI.m:278 */ "Last msg" = "Last msg"; /* File: NNTPSourceGUI.m:346 */ "NNTPSource properties" = "NNTPSource properties"; /* File: NNTPSourceGUI.m:312 */ "Port:" = "Port:"; /* File: NNTPSourceGUI.m:246 */ "Query" = "Query"; /*** Strings from Pref_MessageViewing.m ***/ /* File: Pref_MessageViewing.m:202 */ "Automatically download messages smaller than:" = "Automatically download messages smaller than:"; /* File: Pref_MessageViewing.m:176 */ "Color lines in messages based on quoting depth." = "Color lines in messages based on quoting depth."; /* File: Pref_MessageViewing.m:265 */ "Message font #1:" = "Message font #1:"; /* File: Pref_MessageViewing.m:231 */ "Message font #2:" = "Message font #2:"; /* File: Pref_MessageViewing.m:152 */ "Message viewing" = "Message viewing"; /* File: Pref_MessageViewing.m:157 */ "Message\nviewing" = "Message\nviewing"; /* File: Pref_MessageViewing.m:283 */ /* File: Pref_MessageViewing.m:249 */ "Pick font..." = "Pick font..."; /* File: Pref_MessageViewing.m:183 */ "Scroll intelligently (ie. skip quoted sections and signatures)." = "Scroll intelligently (ie. skip quoted sections and signatures)."; /*** Strings from Pref_Posting.m ***/ /* File: Pref_Posting.m:230 */ "Browse..." = "Browse..."; /* File: Pref_Posting.m:260 */ "Contents of file" = "Contents of file"; /* File: Pref_Posting.m:282 */ "From-address:" = "From-address:"; /* File: Pref_Posting.m:259 */ "Literal" = "Literal"; /* File: Pref_Posting.m:308 */ "Name:" = "Name:"; /* File: Pref_Posting.m:261 */ "Output from program" = "Output from program"; /* File: Pref_Posting.m:192 */ "Post articles as quoted-printable" = "Post articles as quoted-printable"; /* File: Pref_Posting.m:156 */ /* File: Pref_Posting.m:151 */ "Posting" = "Posting"; /* File: Pref_Posting.m:246 */ "Signature type:" = "Signature type:"; /* File: Pref_Posting.m:212 */ "Source:" = "Source:"; /* File: Pref_Posting.m:76 */ "Unable to get signature by running '%@'.\n" = "Unable to get signature by running '%@'.\n"; /* File: Pref_Posting.m:65 */ "Unable to read signature from '%@'.\n" = "Unable to read signature from '%@'.\n"; /*** Strings from Pref_ReadAhead.m ***/ /* File: Pref_ReadAhead.m:181 */ "Next unread past the current thread." = "Next unread past the current thread."; /* File: Pref_ReadAhead.m:188 */ "Next unread." = "Next unread."; /* File: Pref_ReadAhead.m:202 */ "Next." = "Next."; /* File: Pref_ReadAhead.m:195 */ "Previous." = "Previous."; /* File: Pref_ReadAhead.m:216 */ "Read ahead (ie. start downloading related messages\nwhen a message is selected)." = "Read ahead (ie. start downloading related messages\nwhen a message is selected)."; /* File: Pref_ReadAhead.m:123 */ "Read-\nahead" = "Read-\nahead"; /* File: Pref_ReadAhead.m:118 */ "Read-ahead" = "Read-ahead"; /* File: Pref_ReadAhead.m:147 */ "Read-ahead downloads messages smaller than:" = "Read-ahead downloads messages smaller than:"; /*** Strings from Pref_Sources.m ***/ /* File: Pref_Sources.m:97 */ "Id" = "Id"; /* File: Pref_Sources.m:235 */ "Message sources" = "Message sources"; /* File: Pref_Sources.m:218 */ "Message\nsources" = "Message\nsources"; /* File: Pref_Sources.m:158 */ "Properties..." = "Properties..."; /* File: Pref_Sources.m:109 */ "Type" = "Type"; /*** Strings from PreferencesWindowController.m ***/ /* File: PreferencesWindowController.m:118 */ "Preferences" = "Preferences"; /* File: PreferencesWindowController.m:66 */ "Revert" = "Revert"; /* File: PreferencesWindowController.m:74 */ "Save" = "Save"; /*** Strings from main.m ***/ /* File: main.m:270 */ "All" = "All"; /* File: main.m:295 */ "Arrival" = "Arrival"; /* File: main.m:264 */ "Branch" = "Branch"; /* File: main.m:363 */ "Close" = "Close"; /* File: main.m:334 */ "Compose" = "Compose"; /* File: main.m:340 */ "Copy" = "Copy"; /* File: main.m:343 */ "Cut" = "Cut"; /* File: main.m:349 */ "Edit" = "Edit"; /* File: main.m:302 */ "Folder" = "Folder"; /* File: main.m:331 */ "Followup to group" = "Followup to group"; /* File: main.m:375 */ "Hide" = "Hide"; /* File: main.m:225 */ "Info" = "Info"; /* File: main.m:358 */ "Log window" = "Log window"; /* File: main.m:273 */ "Mark read" = "Mark read"; /* File: main.m:323 */ "Message" = "Message"; /* File: main.m:259 */ "Move to" = "Move to"; /* File: main.m:329 */ "New article" = "New article"; /* File: main.m:253 */ "Next branch" = "Next branch"; /* File: main.m:256 */ "Next thread" = "Next thread"; /* File: main.m:247 */ "Next unread" = "Next unread"; /* File: main.m:233 */ "Open folder" = "Open folder"; /* File: main.m:230 */ "Open list" = "Open list"; /* File: main.m:244 */ "Parent" = "Parent"; /* File: main.m:346 */ "Paste" = "Paste"; /* File: main.m:221 */ "Preferences..." = "Preferences..."; /* File: main.m:379 */ "Quit" = "Quit"; /* File: main.m:297 */ "Reverse arrival" = "Reverse arrival"; /* File: main.m:280 */ "Reverse thread" = "Reverse thread"; /* File: main.m:250 */ "Scroll/next" = "Scroll/next"; /* File: main.m:371 */ "Services" = "Services"; /* File: main.m:316 */ "Show source" = "Show source"; /* File: main.m:299 */ "Sort by" = "Sort by"; /* File: main.m:313 */ "Switch font" = "Switch font"; /* File: main.m:278 */ /* File: main.m:267 */ "Thread" = "Thread"; /* File: main.m:310 */ "Toggle read/unread" = "Toggle read/unread"; /* File: main.m:354 */ "Update" = "Update"; /* File: main.m:389 */ "Welcome to LuserNET.app v%s" = "Welcome to LuserNET.app v%s"; /* File: main.m:366 */ "Windows" = "Windows"; /* File: main.m:390 */ "alt-f brings up the folder lists; alt-u checks for new messages" = "alt-f brings up the folder lists; alt-u checks for new messages"; LuserNET-0.4.2/French.lproj/ 40775 764 764 0 10021220141 13551 5ustar alexalexLuserNET-0.4.2/French.lproj/Localizable.strings100664 764 764 35241 10021217655 17547 0ustar alexalex/* Traduit par St\u00e9phane PERON le 17 mars 2002 */ /*** French.lproj/Localizable.strings updated by make_strings 2002-10-03 14:07:02 +0200 add comments above this one ***/ /*** Keys found in multiple places ***/ /* File: NNTPSourceGUI.m:228 */ /* File: Pref_Sources.m:149 */ "Add" = "Ajouter"; /* File: NNTPSourceGUI.m:237 */ /* File: Pref_Sources.m:140 */ "Remove" = "Supprimer"; /* File: FolderListController.m:69 */ /* File: main.m:235 */ "Folder list" = "Liste des groupes"; /* File: FolderListController.m:72 */ /* File: Pref_Sources.m:103 */ "Name" = "Nom"; /* File: MessageViewController.m:288 */ /* File: MessageViewController.m:391 */ /* File: main.m:320 */ "Save..." = "Sauver ..."; /* File: MessageViewController.m:696 */ /* File: main.m:318 */ "Download" = "T\u00e9l\u00e9charger"; /* File: Pref_MessageViewing.m:249 */ /* Flag: untranslated */ /* File: Pref_MessageViewing.m:283 */ /* Flag: untranslated */ "Pick font..." = "Pick font..."; /* File: ComposeWindowController.m:260 */ /* File: MessageViewController.m:550 */ "From:" = "De :"; /* File: ComposeWindowController.m:208 */ /* File: MessageViewController.m:548 */ "Subject:" = "Sujet :"; /* File: Pref_Posting.m:151 */ /* Flag: untranslated */ /* File: Pref_Posting.m:156 */ /* Flag: untranslated */ "Posting" = "Posting"; /*** Unmatched/untranslated keys ***/ /* File: ComposeWindowController.m:234 */ /* Flag: untranslated */ "Newsgroups (separate with commas):" = "Newsgroups (separate with commas):"; /* File: ComposeWindowController.m:279 */ /* Flag: untranslated */ "Post" = "Post"; /* File: MessageViewController.m:286 */ /* Flag: untranslated */ "UUEncoded file:" = "UUEncoded file:"; /* File: MessageViewController.m:363 */ /* Flag: unmatched */ "Unable to render content of class '%@'. Please report this as a bug.\n" = "Impossibilit\u00e9 d'avoir le rendu du contenu de la classe '%@'. Ceci est un bug ; Reportez le s'il vous pla\u00eet.\n"; /* File: MessageViewController.m:372 */ /* Flag: untranslated */ "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n" = "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n"; /* File: MessageViewController.m:540 */ /* Flag: untranslated */ "No icon for part.\n" = "No icon for part.\n"; /* File: MessageViewController.m:692 */ /* Flag: untranslated */ " unknown" = " unknown"; /* File: Pref_MessageViewing.m:152 */ /* Flag: untranslated */ "Message viewing" = "Message viewing"; /* File: Pref_MessageViewing.m:157 */ /* Flag: untranslated */ "Message\nviewing" = "Message\nviewing"; /* File: Pref_MessageViewing.m:176 */ /* Flag: untranslated */ "Color lines in messages based on quoting depth." = "Color lines in messages based on quoting depth."; /* File: Pref_MessageViewing.m:183 */ /* Flag: untranslated */ "Scroll intelligently (ie. skip quoted sections and signatures)." = "Scroll intelligently (ie. skip quoted sections and signatures)."; /* File: Pref_MessageViewing.m:202 */ /* Flag: untranslated */ "Automatically download messages smaller than:" = "Automatically download messages smaller than:"; /* File: Pref_MessageViewing.m:231 */ /* Flag: untranslated */ "Message font #2:" = "Message font #2:"; /* File: Pref_MessageViewing.m:265 */ /* Flag: untranslated */ "Message font #1:" = "Message font #1:"; /* File: Pref_Posting.m:65 */ /* Flag: untranslated */ "Unable to read signature from '%@'.\n" = "Unable to read signature from '%@'.\n"; /* File: Pref_Posting.m:76 */ /* Flag: untranslated */ "Unable to get signature by running '%@'.\n" = "Unable to get signature by running '%@'.\n"; /* File: Pref_Posting.m:192 */ /* Flag: untranslated */ "Post articles as quoted-printable" = "Post articles as quoted-printable"; /* File: Pref_Posting.m:212 */ /* Flag: untranslated */ "Source:" = "Source:"; /* File: Pref_Posting.m:230 */ /* Flag: untranslated */ "Browse..." = "Browse..."; /* File: Pref_Posting.m:246 */ /* Flag: untranslated */ "Signature type:" = "Signature type:"; /* File: Pref_Posting.m:259 */ /* Flag: untranslated */ "Literal" = "Literal"; /* File: Pref_Posting.m:260 */ /* Flag: untranslated */ "Contents of file" = "Contents of file"; /* File: Pref_Posting.m:261 */ /* Flag: untranslated */ "Output from program" = "Output from program"; /* File: Pref_Posting.m:282 */ /* Flag: untranslated */ "From-address:" = "From-address:"; /* File: Pref_Posting.m:308 */ /* Flag: untranslated */ "Name:" = "Name:"; /* File: Pref_ReadAhead.m:118 */ /* Flag: untranslated */ "Read-ahead" = "Read-ahead"; /* File: Pref_ReadAhead.m:123 */ /* Flag: untranslated */ "Read-\nahead" = "Read-\nahead"; /* File: Pref_ReadAhead.m:147 */ /* Flag: untranslated */ "Read-ahead downloads messages smaller than:" = "Read-ahead downloads messages smaller than:"; /* File: Pref_ReadAhead.m:181 */ /* Flag: untranslated */ "Next unread past the current thread." = "Next unread past the current thread."; /* File: Pref_ReadAhead.m:188 */ /* Flag: untranslated */ "Next unread." = "Next unread."; /* File: Pref_ReadAhead.m:195 */ /* Flag: untranslated */ "Previous." = "Previous."; /* File: Pref_ReadAhead.m:202 */ /* Flag: untranslated */ "Next." = "Next."; /* File: Pref_ReadAhead.m:216 */ /* Flag: untranslated */ "Read ahead (ie. start downloading related messages\nwhen a message is selected)." = "Read ahead (ie. start downloading related messages\nwhen a message is selected)."; /* File: Pref_Sources.m:218 */ /* Flag: untranslated */ "Message\nsources" = "Message\nsources"; /* File: Pref_Sources.m:235 */ /* Flag: untranslated */ "Message sources" = "Message sources"; /* File: PreferencesWindowController.m:66 */ /* Flag: untranslated */ "Revert" = "Revert"; /* File: PreferencesWindowController.m:74 */ /* Flag: untranslated */ "Save" = "Save"; /* File: main.m:223 */ /* Flag: untranslated */ "Info..." = "Info..."; /* File: main.m:329 */ /* Flag: untranslated */ "New article" = "New article"; /* File: main.m:331 */ /* Flag: untranslated */ "Followup to group" = "Followup to group"; /* File: main.m:334 */ /* Flag: untranslated */ "Compose" = "Compose"; /* File: main.m:340 */ /* Flag: untranslated */ "Copy" = "Copy"; /* File: main.m:343 */ /* Flag: untranslated */ "Cut" = "Cut"; /* File: main.m:346 */ /* Flag: untranslated */ "Paste" = "Paste"; /* File: main.m:349 */ /* Flag: untranslated */ "Edit" = "Edit"; /*** Strings from FolderListController.m ***/ /* File: FolderListController.m:78 */ "Messages" = "Messages"; /*** Strings from FolderWindowController.m ***/ /* File: FolderWindowController.m:675 */ "Date" = "Date"; /* File: FolderWindowController.m:669 */ "From" = "De"; /* File: FolderWindowController.m:657 */ "Subject" = "Objet"; /*** Strings from LogWindowController.m ***/ /* File: LogWindowController.m:34 */ "Log" = "Log"; /*** Strings from MessageViewController.m ***/ /* File: MessageViewController.m:226 */ "Alternatives:" = "Alternatives:"; /* File: MessageViewController.m:556 */ "Content-type:" = "Content-type:"; /* File: MessageViewController.m:224 */ "Currently shown:" = "Actuellement affich\u00e9 :"; /* File: MessageViewController.m:710 */ "Data cannot be downloaded for %s since it has no source." = "Les donn\u00e9es ne peuvent \u00eatre t\u00e9l\u00e9charg\u00e9es depuis %s s'il n'a pas de source."; /* File: MessageViewController.m:705 */ "Data for %s is currently being downloaded." = "Data for %s is currently being downloaded."; /* File: MessageViewController.m:700 */ "Data for %s is unavailable due to an error." = "Erreur ! Les donn\u00e9es sont indisponibles pour %s."; /* File: MessageViewController.m:554 */ "Date:" = "Date:"; /* File: MessageViewController.m:321 */ "Description:" = "Description:"; /* File: MessageViewController.m:240 */ "Displaying multipart with %i parts: %@\n" = "Displaying multipart with %i parts: %@\n"; /* File: MessageViewController.m:326 */ "Filename:" = "Filename:"; /* File: MessageViewController.m:399 */ "Hide raw data" = "Cacher les ent\u00eates des donn\u00e9es"; /* File: MessageViewController.m:414 */ "Hide text" = "Cacher le texte"; /* File: MessageViewController.m:552 */ "Newsgroups:" = "Groupes de discussion :"; /* File: MessageViewController.m:683 */ "No data has been downloaded for %s." = "No data has been downloaded for %s."; /* File: MessageViewController.m:589 */ "Raw source:\n\n" = "Ent\u00eates du source :\n\n"; /* File: MessageViewController.m:828 */ "Save data" = "Sauver les donn\u00e9es"; /* File: MessageViewController.m:1110 */ "Save message" = "Sauver le message"; /* File: MessageViewController.m:406 */ "Show raw data" = "Voir les ent\u00eates des donn\u00e9es"; /* File: MessageViewController.m:688 */ /* File: MessageViewController.m:387 */ /* File: MessageViewController.m:291 */ "Size:" = "Taille :"; /* File: MessageViewController.m:418 */ "Try to show as text" = "Essayer de l'afficher en tant que texte"; /* File: MessageViewController.m:382 */ "Type:" = "Type :"; /* File: MessageViewController.m:568 */ "Unable to render part of class '%@'. Please report this as a bug.\n" = "Impossibilit\u00e9 d'avoir le rendu d'une partie de la classe '%@'. Ceci est un bug ; Reportez le s'il vous pla\u00eet\n"; /* File: MessageViewController.m:678 */ "Unknown status %i for %s." = "Statut de message %i pour %s inconnu."; /* File: MessageViewController.m:246 */ "\nPart:" = "\nPartie :"; /* File: MessageViewController.m:718 */ "\n\nYou can set a source for this message by clicking on one of the sources:\n\n" = "\n\nVous pouvez d\u00e9finir une source pour ce message en cliquant sur une des sources :\n\n"; /*** Strings from NNTPServer.m ***/ /* File: NNTPServer.m:563 */ "Can't open connection: %s" = "Ne peut ouvrir la connexion : %s"; /* File: NNTPServer.m:1155 */ "Closing connection: %i %s" = "Fermeture de la connexion : %i %s"; /* File: NNTPServer.m:542 */ "Connecting to %i.%i.%i.%i:%i..." = "Connexion \u00e0 %i.%i.%i.%i:%i..."; /* File: NNTPServer.m:1162 */ "New connection (%i total): %i %s" = "Nouvelle connexion (%i total): %i %s"; /* File: NNTPServer.m:476 */ "Resolved '%s' to %i.%i.%i.%i" = "Resolus '%s' to %i.%i.%i.%i"; /* File: NNTPServer.m:1179 */ /* File: NNTPServer.m:1174 */ "Unexpected response when connecting: %i %s" = "R\u00e9ponse inattendue \u00e0 la connexion : %i %s"; /* File: NNTPServer.m:554 */ "can't create socket: %s" = "Impossibilit\u00e9 de cr\u00e9er une socket : %s"; /* File: NNTPServer.m:578 */ "fcntl failed to set non-blocking mode: %s" = "fcntl n'a pas r\u00e9ussi \u00e0 d\u00e9finir le mode non bloquant : %s"; /* File: NNTPServer.m:469 */ "lookup of %s failed: %s" = "Tentative sur %s a \u00e9chou\u00e9 : %s"; /* File: NNTPServer.m:513 */ /* File: NNTPServer.m:500 */ "warning: can't find protocol, assuming 0" = "Attention : ne peut trouver de protocol, assuming 0"; /* File: NNTPServer.m:488 */ "warning: can't find service 'nntp', assuming port 119" = "Attention : ne peut trouver de service 'nntp' sur le port 119"; /*** Strings from NNTPSource.m ***/ /* File: NNTPSource.m:221 */ "%i new messages in '%s', retrieving headers..." = "%i nouveaux messages dans '%s', chargement des ent\u00eates ..."; /* File: NNTPSource.m:165 */ "Downloaded %ikb for '%@'" = "%ikb t\u00e9l\u00e9charg\u00e9s pour '%@'"; /* File: NNTPSource.m:144 */ "Failed to connect: %@" = "Echec de la connexion : %@"; /* File: NNTPSource.m:325 */ "Got headers in '%s': %5i/%5i (%5i) ..." = "Ent\u00eates r\u00e9cup\u00e9r\u00e9s dans '%s': %5i/%5i (%5i) ..."; /* File: NNTPSource.m:338 */ "Got headers in '%s': %5i/%5i (%5i) Done." = "Got headers in '%s': %5i/%5i (%5i) Done."; /* File: NNTPSource.m:184 */ "Group '%s' contains message %i - %i." = "Le groupe '%s' contient le message %i - %i."; /* File: NNTPSource.m:194 */ "Group '%s' doesn't exist." = "Le groupe '%s' n'existe pas."; /* File: NNTPSource.m:209 */ "No new messages in '%s'." = "Pas de nouveau message dans '%s'."; /*** Strings from NNTPSourceGUI.m ***/ /* File: NNTPSourceGUI.m:101 */ "" = ""; /* File: NNTPSourceGUI.m:385 */ "" = ""; /* File: NNTPSourceGUI.m:148 */ "Done" = "Fait"; /* File: NNTPSourceGUI.m:272 */ "Group" = "Groupe"; /* File: NNTPSourceGUI.m:204 */ "Group name:" = "Nom du groupe :"; /* File: NNTPSourceGUI.m:331 */ "Host:" = "H\u00f4te :"; /* File: NNTPSourceGUI.m:185 */ "Last message (use query):" = "Dernier message (utilise une requ\u00eate) :"; /* File: NNTPSourceGUI.m:278 */ "Last msg" = "Dernier msg"; /* File: NNTPSourceGUI.m:346 */ "NNTPSource properties" = "Propri\u00e9t\u00e9s NNTPSource"; /* File: NNTPSourceGUI.m:312 */ "Port:" = "Port :"; /* File: NNTPSourceGUI.m:246 */ "Query" = "Requ\u00eate"; /*** Strings from Pref_Sources.m ***/ /* File: Pref_Sources.m:97 */ "Id" = "Id"; /* File: Pref_Sources.m:158 */ "Properties..." = "Propri\u00e9t\u00e9s ..."; /* File: Pref_Sources.m:109 */ "Type" = "Type"; /*** Strings from PreferencesWindowController.m ***/ /* File: PreferencesWindowController.m:118 */ "Preferences" = "Pr\u00e9f\u00e9rences"; /*** Strings from main.m ***/ /* File: main.m:270 */ "All" = "Tous"; /* File: main.m:295 */ "Arrival" = "Arriv\u00e9e"; /* File: main.m:264 */ "Branch" = "Branche"; /* File: main.m:363 */ "Close" = "Fermer"; /* File: main.m:302 */ "Folder" = "Groupe"; /* File: main.m:375 */ "Hide" = "Cacher"; /* File: main.m:225 */ "Info" = "Informations"; /* File: main.m:358 */ "Log window" = "Fen\u00eatre de log"; /* File: main.m:273 */ "Mark read" = "Marquer comme lu"; /* File: main.m:323 */ "Message" = "Message"; /* File: main.m:259 */ "Move to" = "D\u00e9placer vers"; /* File: main.m:253 */ "Next branch" = "Nouvelle branche"; /* File: main.m:256 */ "Next thread" = "Thread suivant"; /* File: main.m:247 */ "Next unread" = "Non lu suivant"; /* File: main.m:233 */ "Open folder" = "Ouvrir le groupe"; /* File: main.m:230 */ "Open list" = "Ouvrir la liste"; /* File: main.m:244 */ "Parent" = "Parent"; /* File: main.m:221 */ "Preferences..." = "Pr\u00e9f\u00e9rences ..."; /* File: main.m:379 */ "Quit" = "Quit"; /* File: main.m:297 */ "Reverse arrival" = "Arriv\u00e9e invers\u00e9e"; /* File: main.m:280 */ "Reverse thread" = "Thread invers\u00e9"; /* File: main.m:250 */ "Scroll/next" = "D\u00e9placement/Suivant"; /* File: main.m:371 */ "Services" = "Services"; /* File: main.m:316 */ "Show source" = "Voir la source"; /* File: main.m:299 */ "Sort by" = "Trier par"; /* File: main.m:313 */ "Switch font" = "Remplacer la fonte"; /* File: main.m:278 */ /* File: main.m:267 */ "Thread" = "Thread"; /* File: main.m:310 */ "Toggle read/unread" = "Marquer comme lu/non lu"; /* File: main.m:354 */ "Update" = "Mettre \u00e0 jour"; /* File: main.m:389 */ "Welcome to LuserNET.app v%s" = "Bienvenue dans LuserNET.app v%s"; /* File: main.m:366 */ "Windows" = "Fen\u00eatres"; /* File: main.m:390 */ "alt-f brings up the folder lists; alt-u checks for new messages" = "alt-f vous donne la liste des groupes : alt-u v\u00e9rifie les nouveaux messages"; LuserNET-0.4.2/German.lproj/ 40775 764 764 0 10021220141 13555 5ustar alexalexLuserNET-0.4.2/German.lproj/Localizable.strings100664 764 764 32724 10021217655 17556 0ustar alexalex/* German translation by Martin Brecher */ /*** German.lproj/Localizable.strings updated by make_strings 2002-10-03 14:07:02 +0200 add comments above this one ***/ /*** Keys found in multiple places ***/ /* File: FolderListController.m:69 */ /* File: main.m:235 */ "Folder list" = "Ordnerliste"; /* File: FolderListController.m:72 */ /* File: Pref_Sources.m:103 */ "Name" = "Name"; /* File: MessageViewController.m:288 */ /* File: MessageViewController.m:391 */ /* File: main.m:320 */ "Save..." = "Speichern..."; /* File: MessageViewController.m:696 */ /* File: main.m:318 */ "Download" = "Herunterladen"; /* File: NNTPSourceGUI.m:228 */ /* File: Pref_Sources.m:149 */ "Add" = "Hinzuf\u00fcgen"; /* File: NNTPSourceGUI.m:237 */ /* File: Pref_Sources.m:140 */ "Remove" = "Entfernen"; /* File: ComposeWindowController.m:260 */ /* File: MessageViewController.m:550 */ "From:" = "Von:"; /* File: ComposeWindowController.m:208 */ /* File: MessageViewController.m:548 */ "Subject:" = "Betreff:"; /*** Unmatched/untranslated keys ***/ /* File: main.m:223 */ /* Flag: untranslated */ "Info..." = "Info..."; /*** Strings from ComposeWindowController.m ***/ /* File: ComposeWindowController.m:234 */ "Newsgroups (separate with commas):" = "Newsgruppen (von Kommata getrennt):"; /* File: ComposeWindowController.m:279 */ "Post" = "Senden"; /*** Strings from FolderListController.m ***/ /* File: FolderListController.m:78 */ "Messages" = "Nachr."; /*** Strings from FolderWindowController.m ***/ /* File: FolderWindowController.m:675 */ "Date" = "Datum"; /* File: FolderWindowController.m:669 */ "From" = "Von"; /* File: FolderWindowController.m:657 */ "Subject" = "Betreff"; /*** Strings from LogWindowController.m ***/ /* File: LogWindowController.m:34 */ "Log" = "Log"; /*** Strings from MessageViewController.m ***/ /* File: MessageViewController.m:692 */ " unknown" = " unbekannt"; /* File: MessageViewController.m:226 */ "Alternatives:" = "Alternativen:"; /* File: MessageViewController.m:556 */ "Content-type:" = "Content-type:"; /* File: MessageViewController.m:224 */ "Currently shown:" = "Momentan angezeigt:"; /* File: MessageViewController.m:710 */ "Data cannot be downloaded for %s since it has no source." = "Die Datein f\u00fcr %s k\u00f6nnen nicht heruntergeladen werden, da keine Quelle vorhanden ist."; /* File: MessageViewController.m:705 */ "Data for %s is currently being downloaded." = "Die Daten f\u00fcr %s werden momentan heruntergeladen."; /* File: MessageViewController.m:700 */ "Data for %s is unavailable due to an error." = "Die Daten f\u00fcr %s sind auf Grund eines Fehlers nicht verf\u00fcgbar."; /* File: MessageViewController.m:554 */ "Date:" = "Datum:"; /* File: MessageViewController.m:321 */ "Description:" = "Beschreibung:"; /* File: MessageViewController.m:240 */ "Displaying multipart with %i parts: %@\n" = "Zeige Multipartnachricht an mit %i Teilen: %@\n"; /* File: MessageViewController.m:326 */ "Filename:" = "Dateiname:"; /* File: MessageViewController.m:399 */ "Hide raw data" = "Pure Daten ausblenden"; /* File: MessageViewController.m:414 */ "Hide text" = "Text ausblenden"; /* File: MessageViewController.m:552 */ "Newsgroups:" = "Newsgroups:"; /* File: MessageViewController.m:683 */ "No data has been downloaded for %s." = "F\u00fcr %s wurden keinerlei Daten heruntergeladen."; /* File: MessageViewController.m:540 */ "No icon for part.\n" = "Kein Symbol f\u00fcr Teil.\n"; /* File: MessageViewController.m:589 */ "Raw source:\n\n" = "Purer Quelltext:\n\n"; /* File: MessageViewController.m:828 */ "Save data" = "Daten speichern"; /* File: MessageViewController.m:1110 */ "Save message" = "Nachricht speichern"; /* File: MessageViewController.m:406 */ "Show raw data" = "Pure Daten einblenden"; /* File: MessageViewController.m:688 */ /* File: MessageViewController.m:387 */ /* File: MessageViewController.m:291 */ "Size:" = "Gr\u00f6\u00dfe:"; /* File: MessageViewController.m:418 */ "Try to show as text" = "M\u00f6glichst als Text anzeigen"; /* File: MessageViewController.m:382 */ "Type:" = "Typ:"; /* File: MessageViewController.m:286 */ "UUEncoded file:" = "UUEncodeierte Datei:"; /* File: MessageViewController.m:372 */ "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n" = "Kann Inhalt der Klasse '%@' (type '%@') nicht anzeigen. Bitte melde dies als Fehler.\n"; /* File: MessageViewController.m:568 */ "Unable to render part of class '%@'. Please report this as a bug.\n" = "Ein Teil der Klasse '%@' kann nicht dargestellt werden. Bitte melden Sie dies als Fehler .\n"; /* File: MessageViewController.m:678 */ "Unknown status %i for %s." = "Unbekannter Status %i f\u00fcr %s."; /* File: MessageViewController.m:246 */ "\nPart:" = "\nTeil:"; /* File: MessageViewController.m:718 */ "\n\nYou can set a source for this message by clicking on one of the sources:\n\n" = "\n\nSie k\u00f6nnen f\u00fcr diese Nachricht eine Quelle setzen, indem Sie auf eine der folgenden Quellen klicken:\n\n"; /*** Strings from NNTPServer.m ***/ /* File: NNTPServer.m:563 */ "Can't open connection: %s" = "Kann keine Verbindung \u00f6ffnen: %s"; /* File: NNTPServer.m:1155 */ "Closing connection: %i %s" = "Schlie\u00dfe Verbindung: %i %s"; /* File: NNTPServer.m:542 */ "Connecting to %i.%i.%i.%i:%i..." = "Verbinde zu %i.%i.%i.%i:%i..."; /* File: NNTPServer.m:1162 */ "New connection (%i total): %i %s" = "Neue Verbindung (%i insgesamt): %i %s"; /* File: NNTPServer.m:476 */ "Resolved '%s' to %i.%i.%i.%i" = "'%s' zu %i.%i.%i.%i aufgel\u00f6st"; /* File: NNTPServer.m:1179 */ /* File: NNTPServer.m:1174 */ "Unexpected response when connecting: %i %s" = "Unerwartete Antwort beim Verbinden: %i %s"; /* File: NNTPServer.m:554 */ "can't create socket: %s" = "Kann Sockel nicht erstellen: %s"; /* File: NNTPServer.m:578 */ "fcntl failed to set non-blocking mode: %s" = "fcntl k\u00f6nnte nicht-blockenden Modus nicht setzen: %s"; /* File: NNTPServer.m:469 */ "lookup of %s failed: %s" = "Lookup f\u00fcr %s mislungen: %s"; /* File: NNTPServer.m:513 */ /* File: NNTPServer.m:500 */ "warning: can't find protocol, assuming 0" = "Warnung: Kann Protokoll nicht finden, nehme 0 an"; /* File: NNTPServer.m:488 */ "warning: can't find service 'nntp', assuming port 119" = "Warnung: Kann Dienst 'nntp' nicht finden, nehme Port 119 an"; /*** Strings from NNTPSource.m ***/ /* File: NNTPSource.m:221 */ "%i new messages in '%s', retrieving headers..." = "%i neue Nachrichten in '%s', empfange Kopfzeilen..."; /* File: NNTPSource.m:165 */ "Downloaded %ikb for '%@'" = "%ikb f\u00fcr '%@' heruntergeladen"; /* File: NNTPSource.m:144 */ "Failed to connect: %@" = "Verbinden schlug fehl: %@"; /* File: NNTPSource.m:325 */ "Got headers in '%s': %5i/%5i (%5i) ..." = "Kopfzeilen in '%s' empfangend: %5i/%5i (%5i) ..."; /* File: NNTPSource.m:338 */ "Got headers in '%s': %5i/%5i (%5i) Done." = "Kopfzeilen in '%s' empfangend: %5i/%5i (%5i) Fertig."; /* File: NNTPSource.m:184 */ "Group '%s' contains message %i - %i." = "Gruppe '%s' enth\u00e4lt Nachricht %i - %i."; /* File: NNTPSource.m:194 */ "Group '%s' doesn't exist." = "Gruppe '%s' existiert nicht."; /* File: NNTPSource.m:209 */ "No new messages in '%s'." = "Keine neuen Nachrichten in '%s'."; /*** Strings from NNTPSourceGUI.m ***/ /* File: NNTPSourceGUI.m:101 */ "" = ""; /* File: NNTPSourceGUI.m:385 */ "" = ""; /* File: NNTPSourceGUI.m:148 */ "Done" = "Fertig"; /* File: NNTPSourceGUI.m:272 */ "Group" = "Gruppe"; /* File: NNTPSourceGUI.m:204 */ "Group name:" = "Gruppenname:"; /* File: NNTPSourceGUI.m:331 */ "Host:" = "Host:"; /* File: NNTPSourceGUI.m:185 */ "Last message (use query):" = "Letzte Nachricht (durch Pr\u00fcfen):"; /* File: NNTPSourceGUI.m:278 */ "Last msg" = "Letzte Nachr."; /* File: NNTPSourceGUI.m:346 */ "NNTPSource properties" = "NNTPSource Eigenschaften"; /* File: NNTPSourceGUI.m:312 */ "Port:" = "Port:"; /* File: NNTPSourceGUI.m:246 */ "Query" = "Pr\u00fcfen"; /*** Strings from Pref_MessageViewing.m ***/ /* File: Pref_MessageViewing.m:202 */ "Automatically download messages smaller than:" = "Automatisches Herunterladen von Nachrichten kleiner als:"; /* File: Pref_MessageViewing.m:176 */ "Color lines in messages based on quoting depth." = "Farbige Zeilen basierend auf der Zitatebene."; /* File: Pref_MessageViewing.m:265 */ "Message font #1:" = "Nachrichtenschrift Nr. 1:"; /* File: Pref_MessageViewing.m:231 */ "Message font #2:" = "Nachrichtenschrift Nr. 2:"; /* File: Pref_MessageViewing.m:152 */ "Message viewing" = "Nachrichtenansicht"; /* File: Pref_MessageViewing.m:157 */ "Message\nviewing" = "Nachrichten-\nansicht"; /* File: Pref_MessageViewing.m:283 */ /* File: Pref_MessageViewing.m:249 */ "Pick font..." = "Schrift \u00e4ndern..."; /* File: Pref_MessageViewing.m:183 */ "Scroll intelligently (ie. skip quoted sections and signatures)." = "Intelligent scrollen (Zitatbereiche und Signaturen auslassen)."; /*** Strings from Pref_Posting.m ***/ /* File: Pref_Posting.m:230 */ "Browse..." = "Durchsuchen..."; /* File: Pref_Posting.m:260 */ "Contents of file" = "Dateiinhalt"; /* File: Pref_Posting.m:282 */ "From-address:" = "Von-Adresse:"; /* File: Pref_Posting.m:259 */ "Literal" = "W\u00f6rtlich"; /* File: Pref_Posting.m:308 */ "Name:" = "Name:"; /* File: Pref_Posting.m:261 */ "Output from program" = "Programmausgabe"; /* File: Pref_Posting.m:192 */ "Post articles as quoted-printable" = "Artikel als 'quoted-printable' senden"; /* File: Pref_Posting.m:156 */ /* File: Pref_Posting.m:151 */ "Posting" = "Posting"; /* File: Pref_Posting.m:246 */ "Signature type:" = "Signaturtyp:"; /* File: Pref_Posting.m:212 */ "Source:" = "Quelle:"; /* File: Pref_Posting.m:76 */ "Unable to get signature by running '%@'.\n" = "Kann Signatur durch Ausf\u00fchren von '%@' nicht erhalten.\n"; /* File: Pref_Posting.m:65 */ "Unable to read signature from '%@'.\n" = "Kann Signatur von '%@' nicht lesen.\n"; /*** Strings from Pref_ReadAhead.m ***/ /* File: Pref_ReadAhead.m:181 */ "Next unread past the current thread." = "N\u00e4chste ungelesene nach dem aktuellen Thema."; /* File: Pref_ReadAhead.m:188 */ "Next unread." = "N\u00e4chste ungelesene."; /* File: Pref_ReadAhead.m:202 */ "Next." = "N\u00e4chste."; /* File: Pref_ReadAhead.m:195 */ "Previous." = "Vorige."; /* File: Pref_ReadAhead.m:216 */ "Read ahead (ie. start downloading related messages\nwhen a message is selected)." = "Vorauslesen (zugeh\u00f6rige Nachrichten herunterladen,\n wenn eine Nachricht ausgew\u00e4hlt ist)."; /* File: Pref_ReadAhead.m:123 */ "Read-\nahead" = "Voraus-\nlesen"; /* File: Pref_ReadAhead.m:118 */ "Read-ahead" = "Vorauslesen"; /* File: Pref_ReadAhead.m:147 */ "Read-ahead downloads messages smaller than:" = "Vorauslesen l\u00e4dt Nachrichten herunter, wenn kleiner als:"; /*** Strings from Pref_Sources.m ***/ /* File: Pref_Sources.m:97 */ "Id" = "Nr"; /* File: Pref_Sources.m:235 */ "Message sources" = "Nachrichtenquellen"; /* File: Pref_Sources.m:218 */ "Message\nsources" = "Nachrichten-\nquellen"; /* File: Pref_Sources.m:158 */ "Properties..." = "Eigenschaften..."; /* File: Pref_Sources.m:109 */ "Type" = "Typ"; /*** Strings from PreferencesWindowController.m ***/ /* File: PreferencesWindowController.m:118 */ "Preferences" = "Grundeinstellungen"; /* File: PreferencesWindowController.m:66 */ "Revert" = "R\u00fcckg\u00e4ngig"; /* File: PreferencesWindowController.m:74 */ "Save" = "Speichern"; /*** Strings from main.m ***/ /* File: main.m:270 */ "All" = "Alle"; /* File: main.m:295 */ "Arrival" = "Ankunft"; /* File: main.m:264 */ "Branch" = "Zweig"; /* File: main.m:363 */ "Close" = "Schlie\u00dfen"; /* File: main.m:334 */ "Compose" = "Erstellen"; /* File: main.m:340 */ "Copy" = "Kopieren"; /* File: main.m:343 */ "Cut" = "Aussschneiden"; /* File: main.m:349 */ "Edit" = "Bearbeiten"; /* File: main.m:302 */ "Folder" = "Ordner"; /* File: main.m:331 */ "Followup to group" = "'Followup' zu Gruppe"; /* File: main.m:375 */ "Hide" = "Ausblenden"; /* File: main.m:225 */ "Info" = "Info"; /* File: main.m:358 */ "Log window" = "Logfenster"; /* File: main.m:273 */ "Mark read" = "Als gelesen markieren"; /* File: main.m:323 */ "Message" = "Nachricht"; /* File: main.m:259 */ "Move to" = "Verschieben nach"; /* File: main.m:329 */ "New article" = "Neuer Artikel"; /* File: main.m:253 */ "Next branch" = "N\u00e4chster Zweig"; /* File: main.m:256 */ "Next thread" = "N\u00e4chstes Thema"; /* File: main.m:247 */ "Next unread" = "Next unread"; /* File: main.m:233 */ "Open folder" = "Ordner \u00f6ffnen"; /* File: main.m:230 */ "Open list" = "Liste \u00f6ffnen"; /* File: main.m:244 */ "Parent" = "\u00dcbergeordnete"; /* File: main.m:346 */ "Paste" = "Einf\u00fcgen"; /* File: main.m:221 */ "Preferences..." = "Grundeinstellungen..."; /* File: main.m:379 */ "Quit" = "Verlassen"; /* File: main.m:297 */ "Reverse arrival" = "Umgekehrte Ankunft"; /* File: main.m:280 */ "Reverse thread" = "Umgekehrtes Thema"; /* File: main.m:250 */ "Scroll/next" = "Scrollen/n\u00e4chste"; /* File: main.m:371 */ "Services" = "Dienste"; /* File: main.m:316 */ "Show source" = "Quelltext anzeigen"; /* File: main.m:299 */ "Sort by" = "Sortieren nach"; /* File: main.m:313 */ "Switch font" = "Schrift \u00e4ndern"; /* File: main.m:278 */ /* File: main.m:267 */ "Thread" = "Thema"; /* File: main.m:310 */ "Toggle read/unread" = "gelesen/ungelesen umschalten"; /* File: main.m:354 */ "Update" = "Aktualisieren"; /* File: main.m:389 */ "Welcome to LuserNET.app v%s" = "Willkommen bei LuserNET.app v%s"; /* File: main.m:366 */ "Windows" = "Fenster"; /* File: main.m:390 */ "alt-f brings up the folder lists; alt-u checks for new messages" = "alt-f \u00f6ffnet die Ordnerliste; alt-u pr\u00fcft auf neue Nachrichten"; LuserNET-0.4.2/Spanish.lproj/ 40775 764 764 0 10021220141 13751 5ustar alexalexLuserNET-0.4.2/Spanish.lproj/Localizable.strings100664 764 764 36047 10021217656 17755 0ustar alexalex/* Spanish translation by Quique */ /*** Spanish.lproj/Localizable.strings updated by make_strings 2002-10-03 14:07:01 +0200 add comments above this one ***/ /*** Keys found in multiple places ***/ /* File: NNTPSourceGUI.m:228 */ /* File: Pref_Sources.m:149 */ "Add" = "A\u00f1adir"; /* File: NNTPSourceGUI.m:237 */ /* File: Pref_Sources.m:140 */ "Remove" = "Quitar"; /* File: FolderListController.m:69 */ /* File: main.m:235 */ "Folder list" = "Lista de carpetas"; /* File: FolderListController.m:72 */ /* File: Pref_Sources.m:103 */ "Name" = "Nombre"; /* File: MessageViewController.m:288 */ /* File: MessageViewController.m:391 */ /* File: main.m:320 */ "Save..." = "Guardar..."; /* File: MessageViewController.m:291 */ /* Flag: untranslated */ /* File: MessageViewController.m:387 */ /* Flag: untranslated */ /* File: MessageViewController.m:688 */ /* Flag: untranslated */ "Size:" = "Size:"; /* File: MessageViewController.m:696 */ /* Flag: untranslated */ /* File: main.m:318 */ /* Flag: untranslated */ "Download" = "Download"; /* File: Pref_MessageViewing.m:249 */ /* Flag: untranslated */ /* File: Pref_MessageViewing.m:283 */ /* Flag: untranslated */ "Pick font..." = "Pick font..."; /* File: ComposeWindowController.m:208 */ /* Flag: untranslated */ /* File: MessageViewController.m:548 */ /* Flag: untranslated */ "Subject:" = "Subject:"; /* File: ComposeWindowController.m:260 */ /* Flag: untranslated */ /* File: MessageViewController.m:550 */ /* Flag: untranslated */ "From:" = "From:"; /* File: Pref_Posting.m:151 */ /* Flag: untranslated */ /* File: Pref_Posting.m:156 */ /* Flag: untranslated */ "Posting" = "Posting"; /*** Unmatched/untranslated keys ***/ /* File: ComposeWindowController.m:234 */ /* Flag: untranslated */ "Newsgroups (separate with commas):" = "Newsgroups (separate with commas):"; /* File: ComposeWindowController.m:279 */ /* Flag: untranslated */ "Post" = "Post"; /* File: MessageViewController.m:224 */ /* Flag: untranslated */ "Currently shown:" = "Currently shown:"; /* File: MessageViewController.m:226 */ /* Flag: untranslated */ "Alternatives:" = "Alternatives:"; /* File: MessageViewController.m:240 */ /* Flag: untranslated */ "Displaying multipart with %i parts: %@\n" = "Displaying multipart with %i parts: %@\n"; /* File: MessageViewController.m:246 */ /* Flag: untranslated */ "\nPart:" = "\nPart:"; /* File: MessageViewController.m:286 */ /* Flag: untranslated */ "UUEncoded file:" = "UUEncoded file:"; /* File: MessageViewController.m:321 */ /* Flag: untranslated */ "Description:" = "Description:"; /* File: MessageViewController.m:326 */ /* Flag: untranslated */ "Filename:" = "Filename:"; /* File: MessageViewController.m:372 */ /* Flag: untranslated */ "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n" = "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n"; /* File: MessageViewController.m:382 */ /* Flag: untranslated */ "Type:" = "Type:"; /* File: MessageViewController.m:399 */ /* Flag: untranslated */ "Hide raw data" = "Hide raw data"; /* File: MessageViewController.m:406 */ /* Flag: untranslated */ "Show raw data" = "Show raw data"; /* File: MessageViewController.m:414 */ /* Flag: untranslated */ "Hide text" = "Hide text"; /* File: MessageViewController.m:418 */ /* Flag: untranslated */ "Try to show as text" = "Try to show as text"; /* File: MessageViewController.m:540 */ /* Flag: untranslated */ "No icon for part.\n" = "No icon for part.\n"; /* File: MessageViewController.m:552 */ /* Flag: untranslated */ "Newsgroups:" = "Newsgroups:"; /* File: MessageViewController.m:556 */ /* Flag: untranslated */ "Content-type:" = "Content-type:"; /* File: MessageViewController.m:568 */ /* Flag: untranslated */ "Unable to render part of class '%@'. Please report this as a bug.\n" = "Unable to render part of class '%@'. Please report this as a bug.\n"; /* File: MessageViewController.m:589 */ /* Flag: untranslated */ "Raw source:\n\n" = "Raw source:\n\n"; /* File: MessageViewController.m:692 */ /* Flag: untranslated */ " unknown" = " unknown"; /* File: MessageViewController.m:718 */ /* Flag: untranslated */ "\n\nYou can set a source for this message by clicking on one of the sources:\n\n" = "\n\nYou can set a source for this message by clicking on one of the sources:\n\n"; /* File: MessageViewController.m:828 */ /* Flag: untranslated */ "Save data" = "Save data"; /* File: NNTPSource.m:228 */ /* Flag: unmatched */ "got headers for %i new messages in '%s'" = "se han obtenido las cabeceras de %i mensajes nuevos en '%s'"; /* File: NNTPSource.m:325 */ /* Flag: untranslated */ "Got headers in '%s': %5i/%5i (%5i) ..." = "Got headers in '%s': %5i/%5i (%5i) ..."; /* File: NNTPSource.m:338 */ /* Flag: untranslated */ "Got headers in '%s': %5i/%5i (%5i) Done." = "Got headers in '%s': %5i/%5i (%5i) Done."; /* File: Pref_MessageViewing.m:152 */ /* Flag: untranslated */ "Message viewing" = "Message viewing"; /* File: Pref_MessageViewing.m:157 */ /* Flag: untranslated */ "Message\nviewing" = "Message\nviewing"; /* File: Pref_MessageViewing.m:176 */ /* Flag: untranslated */ "Color lines in messages based on quoting depth." = "Color lines in messages based on quoting depth."; /* File: Pref_MessageViewing.m:183 */ /* Flag: untranslated */ "Scroll intelligently (ie. skip quoted sections and signatures)." = "Scroll intelligently (ie. skip quoted sections and signatures)."; /* File: Pref_MessageViewing.m:202 */ /* Flag: untranslated */ "Automatically download messages smaller than:" = "Automatically download messages smaller than:"; /* File: Pref_MessageViewing.m:231 */ /* Flag: untranslated */ "Message font #2:" = "Message font #2:"; /* File: Pref_MessageViewing.m:265 */ /* Flag: untranslated */ "Message font #1:" = "Message font #1:"; /* File: Pref_Posting.m:65 */ /* Flag: untranslated */ "Unable to read signature from '%@'.\n" = "Unable to read signature from '%@'.\n"; /* File: Pref_Posting.m:76 */ /* Flag: untranslated */ "Unable to get signature by running '%@'.\n" = "Unable to get signature by running '%@'.\n"; /* File: Pref_Posting.m:192 */ /* Flag: untranslated */ "Post articles as quoted-printable" = "Post articles as quoted-printable"; /* File: Pref_Posting.m:212 */ /* Flag: untranslated */ "Source:" = "Source:"; /* File: Pref_Posting.m:230 */ /* Flag: untranslated */ "Browse..." = "Browse..."; /* File: Pref_Posting.m:246 */ /* Flag: untranslated */ "Signature type:" = "Signature type:"; /* File: Pref_Posting.m:259 */ /* Flag: untranslated */ "Literal" = "Literal"; /* File: Pref_Posting.m:260 */ /* Flag: untranslated */ "Contents of file" = "Contents of file"; /* File: Pref_Posting.m:261 */ /* Flag: untranslated */ "Output from program" = "Output from program"; /* File: Pref_Posting.m:282 */ /* Flag: untranslated */ "From-address:" = "From-address:"; /* File: Pref_Posting.m:308 */ /* Flag: untranslated */ "Name:" = "Name:"; /* File: Pref_ReadAhead.m:118 */ /* Flag: untranslated */ "Read-ahead" = "Read-ahead"; /* File: Pref_ReadAhead.m:123 */ /* Flag: untranslated */ "Read-\nahead" = "Read-\nahead"; /* File: Pref_ReadAhead.m:147 */ /* Flag: untranslated */ "Read-ahead downloads messages smaller than:" = "Read-ahead downloads messages smaller than:"; /* File: Pref_ReadAhead.m:181 */ /* Flag: untranslated */ "Next unread past the current thread." = "Next unread past the current thread."; /* File: Pref_ReadAhead.m:188 */ /* Flag: untranslated */ "Next unread." = "Next unread."; /* File: Pref_ReadAhead.m:195 */ /* Flag: untranslated */ "Previous." = "Previous."; /* File: Pref_ReadAhead.m:202 */ /* Flag: untranslated */ "Next." = "Next."; /* File: Pref_ReadAhead.m:216 */ /* Flag: untranslated */ "Read ahead (ie. start downloading related messages\nwhen a message is selected)." = "Read ahead (ie. start downloading related messages\nwhen a message is selected)."; /* File: Pref_Sources.m:218 */ /* Flag: untranslated */ "Message\nsources" = "Message\nsources"; /* File: Pref_Sources.m:235 */ /* Flag: untranslated */ "Message sources" = "Message sources"; /* File: PreferencesWindowController.m:66 */ /* Flag: untranslated */ "Revert" = "Revert"; /* File: PreferencesWindowController.m:74 */ /* Flag: untranslated */ "Save" = "Save"; /* File: PreferencesWindowController.m:146 */ /* Flag: unmatched */ "OK" = "OK"; /* File: main.m:223 */ /* Flag: untranslated */ "Info..." = "Info..."; /* File: main.m:313 */ /* Flag: untranslated */ "Switch font" = "Switch font"; /* File: main.m:316 */ /* Flag: untranslated */ "Show source" = "Show source"; /* File: main.m:329 */ /* Flag: untranslated */ "New article" = "New article"; /* File: main.m:331 */ /* Flag: untranslated */ "Followup to group" = "Followup to group"; /* File: main.m:334 */ /* Flag: untranslated */ "Compose" = "Compose"; /* File: main.m:340 */ /* Flag: untranslated */ "Copy" = "Copy"; /* File: main.m:343 */ /* Flag: untranslated */ "Cut" = "Cut"; /* File: main.m:346 */ /* Flag: untranslated */ "Paste" = "Paste"; /* File: main.m:349 */ /* Flag: untranslated */ "Edit" = "Edit"; /*** Strings from FolderListController.m ***/ /* File: FolderListController.m:78 */ "Messages" = "Mensajes"; /*** Strings from FolderWindowController.m ***/ /* File: FolderWindowController.m:675 */ "Date" = "Fecha"; /* File: FolderWindowController.m:669 */ "From" = "De"; /* File: FolderWindowController.m:657 */ "Subject" = "Asunto"; /*** Strings from LogWindowController.m ***/ /* File: LogWindowController.m:34 */ "Log" = "Cuaderno de bit\u00e1cora"; /*** Strings from MessageViewController.m ***/ /* File: MessageViewController.m:710 */ "Data cannot be downloaded for %s since it has no source." = "No se pueden descargar los datos de %s porque no tiene fuente."; /* File: MessageViewController.m:705 */ "Data for %s is currently being downloaded." = "Los datos de %s se est\u00e1n descargando ahora."; /* File: MessageViewController.m:700 */ "Data for %s is unavailable due to an error." = "Los datos de %s no est\u00e1n disponibles debido a un error."; /* File: MessageViewController.m:554 */ "Date:" = "Fecha:"; /* File: MessageViewController.m:683 */ "No data has been downloaded for %s." = "No se han descargado datos de %s."; /* File: MessageViewController.m:1110 */ "Save message" = "Guardar mensaje"; /* File: MessageViewController.m:678 */ "Unknown status %i for %s." = "Estado desconocido %i de %s."; /*** Strings from NNTPServer.m ***/ /* File: NNTPServer.m:563 */ "Can't open connection: %s" = "No se puede abrir la conexi\u00f3n: %s"; /* File: NNTPServer.m:1155 */ "Closing connection: %i %s" = "Cerrando la conexi\u00f3n: %i %s"; /* File: NNTPServer.m:542 */ "Connecting to %i.%i.%i.%i:%i..." = "Conectando a %i.%i.%i.%i:%i..."; /* File: NNTPServer.m:1162 */ "New connection (%i total): %i %s" = "Nueva conexi\u00f3n (%i en total): %i %s"; /* File: NNTPServer.m:476 */ "Resolved '%s' to %i.%i.%i.%i" = "Resuelto '%s' a %i.%i.%i.%i"; /* File: NNTPServer.m:1179 */ /* File: NNTPServer.m:1174 */ "Unexpected response when connecting: %i %s" = "Respuesta inesperada al conectar: %i %s"; /* File: NNTPServer.m:554 */ "can't create socket: %s" = "no se puede crear el socket: %s"; /* File: NNTPServer.m:578 */ "fcntl failed to set non-blocking mode: %s" = "fcntl no ha podido establecer el modo no-bloqueante: %s"; /* File: NNTPServer.m:469 */ "lookup of %s failed: %s" = "no se ha podido resolver %s: %s"; /* File: NNTPServer.m:513 */ /* File: NNTPServer.m:500 */ "warning: can't find protocol, assuming 0" = "advertencia: no se puede encontrar el protocolo, se asume 0"; /* File: NNTPServer.m:488 */ "warning: can't find service 'nntp', assuming port 119" = "advertencia: no se puede encontrar el servicio 'nntp', se asume el puerto 119"; /*** Strings from NNTPSource.m ***/ /* File: NNTPSource.m:221 */ "%i new messages in '%s', retrieving headers..." = "%i mensajes nuevos en '%s', obteniendo las cabeceras..."; /* File: NNTPSource.m:165 */ "Downloaded %ikb for '%@'" = "Descargados %ikb de '%@'"; /* File: NNTPSource.m:144 */ "Failed to connect: %@" = "No se ha podido conectar: %@"; /* File: NNTPSource.m:184 */ "Group '%s' contains message %i - %i." = "El grupo '%s' contiene los mensajes %i - %i."; /* File: NNTPSource.m:194 */ "Group '%s' doesn't exist." = "El grupo '%s' no existe."; /* File: NNTPSource.m:209 */ "No new messages in '%s'." = "No hay mensajes nuevos en '%s'."; /*** Strings from NNTPSourceGUI.m ***/ /* File: NNTPSourceGUI.m:101 */ "" = ""; /* File: NNTPSourceGUI.m:385 */ "" = ""; /* File: NNTPSourceGUI.m:148 */ "Done" = "Hecho"; /* File: NNTPSourceGUI.m:272 */ "Group" = "Grupo"; /* File: NNTPSourceGUI.m:204 */ "Group name:" = "Nombre del grupo:"; /* File: NNTPSourceGUI.m:331 */ "Host:" = "Servidor:"; /* File: NNTPSourceGUI.m:185 */ "Last message (use query):" = "\u00daltimo mensaje (usar petici\u00f3n):"; /* File: NNTPSourceGUI.m:278 */ "Last msg" = "\u00daltimo mensaje"; /* File: NNTPSourceGUI.m:346 */ "NNTPSource properties" = "Propiedades de NNTPSource"; /* File: NNTPSourceGUI.m:312 */ "Port:" = "Puerto:"; /* File: NNTPSourceGUI.m:246 */ "Query" = "Petici\u00f3n"; /*** Strings from Pref_Sources.m ***/ /* File: Pref_Sources.m:97 */ "Id" = "Id"; /* File: Pref_Sources.m:158 */ "Properties..." = "Propiedades..."; /* File: Pref_Sources.m:109 */ "Type" = "Tipo"; /*** Strings from PreferencesWindowController.m ***/ /* File: PreferencesWindowController.m:118 */ "Preferences" = "Preferencias"; /*** Strings from main.m ***/ /* File: main.m:270 */ "All" = "Todo"; /* File: main.m:295 */ "Arrival" = "Llegada"; /* File: main.m:264 */ "Branch" = "Rama"; /* File: main.m:363 */ "Close" = "Cerrar"; /* File: main.m:302 */ "Folder" = "Carpeta"; /* File: main.m:375 */ "Hide" = "Ocultar"; /* File: main.m:225 */ "Info" = "Informaci\u00f3n"; /* File: main.m:358 */ "Log window" = "Cuaderno de bit\u00e1cora"; /* File: main.m:273 */ "Mark read" = "Marcar como le\u00eddo"; /* File: main.m:323 */ "Message" = "Mensaje"; /* File: main.m:259 */ "Move to" = "Mover a"; /* File: main.m:253 */ "Next branch" = "Siguiente rama"; /* File: main.m:256 */ "Next thread" = "Siguiente hilo"; /* File: main.m:247 */ "Next unread" = "Siguiente no le\u00eddo"; /* File: main.m:233 */ "Open folder" = "Abrir carpeta"; /* File: main.m:230 */ "Open list" = "Abrir lista"; /* File: main.m:244 */ "Parent" = "Padre"; /* File: main.m:221 */ "Preferences..." = "Preferencias..."; /* File: main.m:379 */ "Quit" = "Salir"; /* File: main.m:297 */ "Reverse arrival" = "Invertir llegada"; /* File: main.m:280 */ "Reverse thread" = "Invertir hilo"; /* File: main.m:250 */ "Scroll/next" = "Desplazar/siguiente"; /* File: main.m:371 */ "Services" = "Servicios"; /* File: main.m:299 */ "Sort by" = "Ordenar por"; /* File: main.m:278 */ /* File: main.m:267 */ "Thread" = "Hilo"; /* File: main.m:310 */ "Toggle read/unread" = "Cambiar le\u00eddo/no le\u00eddo"; /* File: main.m:354 */ "Update" = "Actualizar"; /* File: main.m:389 */ "Welcome to LuserNET.app v%s" = "Bienvenido a LuserNET.app v%s"; /* File: main.m:366 */ "Windows" = "Ventanas"; /* File: main.m:390 */ "alt-f brings up the folder lists; alt-u checks for new messages" = "alt-f saca las listas de carpetas; alt-u comprueba si hay mensajes nuevos"; LuserNET-0.4.2/Swedish.lproj/ 40775 764 764 0 10021220141 13752 5ustar alexalexLuserNET-0.4.2/Swedish.lproj/Localizable.strings100664 764 764 32512 10021217656 17747 0ustar alexalex/* Swedish translation by Alexander Malmberg */ /*** Swedish.lproj/Localizable.strings updated by make_strings 2002-10-03 14:07:01 +0200 add comments above this one ***/ /*** Keys found in multiple places ***/ /* File: NNTPSourceGUI.m:228 */ /* File: Pref_Sources.m:149 */ "Add" = "L\u00e4gg till"; /* File: NNTPSourceGUI.m:237 */ /* File: Pref_Sources.m:140 */ "Remove" = "Ta bort"; /* File: FolderListController.m:69 */ /* File: main.m:235 */ "Folder list" = "Mapp-lista"; /* File: FolderListController.m:72 */ /* File: Pref_Sources.m:103 */ "Name" = "Namn"; /* File: MessageViewController.m:288 */ /* File: MessageViewController.m:391 */ /* File: main.m:320 */ "Save..." = "Spara..."; /* File: MessageViewController.m:696 */ /* File: main.m:318 */ "Download" = "Ladda ner"; /* File: ComposeWindowController.m:260 */ /* File: MessageViewController.m:550 */ "From:" = "Fr\u00e5n:"; /* File: ComposeWindowController.m:208 */ /* File: MessageViewController.m:548 */ "Subject:" = "\u00c4mne:"; /*** Unmatched/untranslated keys ***/ /* File: main.m:223 */ "Info..." = "Info..."; /*** Strings from ComposeWindowController.m ***/ /* File: ComposeWindowController.m:234 */ "Newsgroups (separate with commas):" = "Grupper (kommaseparerad lista):"; /* File: ComposeWindowController.m:279 */ "Post" = "Skicka"; /*** Strings from FolderListController.m ***/ /* File: FolderListController.m:78 */ "Messages" = "Meddelanden"; /*** Strings from FolderWindowController.m ***/ /* File: FolderWindowController.m:675 */ "Date" = "Datum"; /* File: FolderWindowController.m:669 */ "From" = "Fr\u00e5n"; /* File: FolderWindowController.m:657 */ "Subject" = "\u00c4mne"; /*** Strings from LogWindowController.m ***/ /* File: LogWindowController.m:34 */ "Log" = "Logg"; /*** Strings from MessageViewController.m ***/ /* File: MessageViewController.m:692 */ " unknown" = " ok\u00e4nd"; /* File: MessageViewController.m:226 */ "Alternatives:" = "Alternativ:"; /* File: MessageViewController.m:556 */ "Content-type:" = "Inneh\u00e5llstyp:"; /* File: MessageViewController.m:224 */ "Currently shown:" = "Visar nu:"; /* File: MessageViewController.m:710 */ "Data cannot be downloaded for %s since it has no source." = "Data f\u00f6r %s kan inte laddas ner eftersom meddelandet inte har n\u00e5gon k\u00e4lla."; /* File: MessageViewController.m:705 */ "Data for %s is currently being downloaded." = "Data f\u00f6r %s h\u00e5ller p\u00e5 att laddas ner."; /* File: MessageViewController.m:700 */ "Data for %s is unavailable due to an error." = "Data f\u00f6r %s are otillg\u00e4nglig p\u00e5 grund av ett fel."; /* File: MessageViewController.m:554 */ "Date:" = "Datum:"; /* File: MessageViewController.m:321 */ "Description:" = "Beskrivning:"; /* File: MessageViewController.m:240 */ "Displaying multipart with %i parts: %@\n" = "Visar multipart med %i delar: %@\n"; /* File: MessageViewController.m:326 */ "Filename:" = "Filnamn:"; /* File: MessageViewController.m:399 */ "Hide raw data" = "Visa inte r\u00e5 data"; /* File: MessageViewController.m:414 */ "Hide text" = "G\u00f6m text"; /* File: MessageViewController.m:552 */ "Newsgroups:" = "Grupper:"; /* File: MessageViewController.m:683 */ "No data has been downloaded for %s." = "Ingen data har laddats ner f\u00f6r %s."; /* File: MessageViewController.m:540 */ "No icon for part.\n" = "Ingen ikon f\u00f6r delen.\n"; /* File: MessageViewController.m:589 */ "Raw source:\n\n" = "R\u00e5 k\u00e4ll-data:\n\n"; /* File: MessageViewController.m:828 */ "Save data" = "Spara data"; /* File: MessageViewController.m:1110 */ "Save message" = "Spara meddelande"; /* File: MessageViewController.m:406 */ "Show raw data" = "Visa r\u00e5 data"; /* File: MessageViewController.m:688 */ /* File: MessageViewController.m:387 */ /* File: MessageViewController.m:291 */ "Size:" = "Storlek:"; /* File: MessageViewController.m:418 */ "Try to show as text" = "F\u00f6rs\u00f6k visa som text"; /* File: MessageViewController.m:382 */ "Type:" = "Typ:"; /* File: MessageViewController.m:286 */ "UUEncoded file:" = "UUEncoded fil:"; /* File: MessageViewController.m:372 */ "Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n" = "Kunde inte visa inneh\u00e5ll av klass '%@' (typ '%@'). V\u00e4nligen rapportera detta som en bug.\n"; /* File: MessageViewController.m:568 */ "Unable to render part of class '%@'. Please report this as a bug.\n" = "Kunde inte visa del av klass '%@'. V\u00e4nligen rapportera detta som en bug.\n"; /* File: MessageViewController.m:678 */ "Unknown status %i for %s." = "Ok\u00e4nd status %i f\u00f6r %s."; /* File: MessageViewController.m:246 */ "\nPart:" = "\nDel:"; /* File: MessageViewController.m:718 */ "\n\nYou can set a source for this message by clicking on one of the sources:\n\n" = "\n\nDu kan v\u00e4lja en k\u00e4lla f\u00f6r det h\u00e4r meddelandet genom att klicka p\u00e5 en av k\u00e4llorna:\n\n"; /*** Strings from NNTPServer.m ***/ /* File: NNTPServer.m:563 */ "Can't open connection: %s" = "Kunde inte \u00f6ppna anslutning: %s"; /* File: NNTPServer.m:1155 */ "Closing connection: %i %s" = "St\u00e4nger anslutning: %i %s"; /* File: NNTPServer.m:542 */ "Connecting to %i.%i.%i.%i:%i..." = "Ansluter till %i.%i.%i.%i:%i..."; /* File: NNTPServer.m:1162 */ "New connection (%i total): %i %s" = "Ny anslutning (%i totalt): %i %s"; /* File: NNTPServer.m:476 */ "Resolved '%s' to %i.%i.%i.%i" = "Resolved '%s' to %i.%i.%i.%i"; /* File: NNTPServer.m:1179 */ /* File: NNTPServer.m:1174 */ "Unexpected response when connecting: %i %s" = "Ov\u00e4ntat svar vid anslutning: %i %s"; /* File: NNTPServer.m:554 */ "can't create socket: %s" = "can't create socket: %s"; /* File: NNTPServer.m:578 */ "fcntl failed to set non-blocking mode: %s" = "fcntl failed to set non-blocking mode: %s"; /* File: NNTPServer.m:469 */ "lookup of %s failed: %s" = "lookup of %s failed: %s"; /* File: NNTPServer.m:513 */ /* File: NNTPServer.m:500 */ "warning: can't find protocol, assuming 0" = "warning: can't find protocol, assuming 0"; /* File: NNTPServer.m:488 */ "warning: can't find service 'nntp', assuming port 119" = "warning: can't find service 'nntp', assuming port 119"; /*** Strings from NNTPSource.m ***/ /* File: NNTPSource.m:221 */ "%i new messages in '%s', retrieving headers..." = "%i ny meddelanden i '%s', ladder ner headers..."; /* File: NNTPSource.m:165 */ "Downloaded %ikb for '%@'" = "Laddat ner %ikb f\u00f6r '%@'..."; /* File: NNTPSource.m:144 */ "Failed to connect: %@" = "Kunde inte ansluta: %@"; /* File: NNTPSource.m:325 */ "Got headers in '%s': %5i/%5i (%5i) ..." = "Fick headers i '%s': %5i/%5i (%5i) ..."; /* File: NNTPSource.m:338 */ "Got headers in '%s': %5i/%5i (%5i) Done." = "Fick headers i '%s': %5i/%5i (%5i) Klar."; /* File: NNTPSource.m:184 */ "Group '%s' contains message %i - %i." = "Gruppen '%s' inneh\u00e5ller meddelandena %i - %i."; /* File: NNTPSource.m:194 */ "Group '%s' doesn't exist." = "Gruppen '%s' finns inte."; /* File: NNTPSource.m:209 */ "No new messages in '%s'." = "Inga nya meddelanden i '%s'."; /*** Strings from NNTPSourceGUI.m ***/ /* File: NNTPSourceGUI.m:101 */ "" = ""; /* File: NNTPSourceGUI.m:385 */ "" = ""; /* File: NNTPSourceGUI.m:148 */ "Done" = "St\u00e4ng"; /* File: NNTPSourceGUI.m:272 */ "Group" = "Grupp"; /* File: NNTPSourceGUI.m:204 */ "Group name:" = "Gruppnamn:"; /* File: NNTPSourceGUI.m:331 */ "Host:" = "Host:"; /* File: NNTPSourceGUI.m:185 */ "Last message (use query):" = "Senaste meddelande (anv\u00e4nd query):"; /* File: NNTPSourceGUI.m:278 */ "Last msg" = "Senaste meddelande"; /* File: NNTPSourceGUI.m:346 */ "NNTPSource properties" = "NNTPSource egenskaper"; /* File: NNTPSourceGUI.m:312 */ "Port:" = "Port:"; /* File: NNTPSourceGUI.m:246 */ "Query" = "Query"; /*** Strings from Pref_MessageViewing.m ***/ /* File: Pref_MessageViewing.m:202 */ "Automatically download messages smaller than:" = "Ladda automatiskt ner meddelanden mindre \u00e4n:"; /* File: Pref_MessageViewing.m:176 */ "Color lines in messages based on quoting depth." = "F\u00e4rgl\u00e4gg rader i meddelanden beroende p\u00e5 citeringsniv\u00e5n."; /* File: Pref_MessageViewing.m:265 */ "Message font #1:" = "Meddelandetypsnitt #1:"; /* File: Pref_MessageViewing.m:231 */ "Message font #2:" = "Meddelandetypsnitt #2:"; /* File: Pref_MessageViewing.m:152 */ "Message viewing" = "Meddelandevisning"; /* File: Pref_MessageViewing.m:157 */ "Message\nviewing" = "Meddelande-\nvisning"; /* File: Pref_MessageViewing.m:283 */ /* File: Pref_MessageViewing.m:249 */ "Pick font..." = "V\u00e4lj typsnitt..."; /* File: Pref_MessageViewing.m:183 */ "Scroll intelligently (ie. skip quoted sections and signatures)." = "Scrolla intelligent (dvs. hoppa \u00f6ver citerade omraden och signaturer)."; /*** Strings from Pref_Posting.m ***/ /* File: Pref_Posting.m:230 */ "Browse..." = "Bl\u00e4ddra..."; /* File: Pref_Posting.m:260 */ "Contents of file" = "Filinneh\u00e5ll"; /* File: Pref_Posting.m:282 */ "From-address:" = "Fr\u00e5n-adress:"; /* File: Pref_Posting.m:259 */ "Literal" = "F\u00e4ltet"; /* File: Pref_Posting.m:308 */ "Name:" = "Namn:"; /* File: Pref_Posting.m:261 */ "Output from program" = "Utskrift fr\u00e5n program"; /* File: Pref_Posting.m:192 */ "Post articles as quoted-printable" = "Skicka artiklar som quoted-printable"; /* File: Pref_Posting.m:156 */ /* File: Pref_Posting.m:151 */ "Posting" = "S\u00e4ndning"; /* File: Pref_Posting.m:246 */ "Signature type:" = "Signaturtyp:"; /* File: Pref_Posting.m:212 */ "Source:" = "K\u00e4lla:"; /* File: Pref_Posting.m:76 */ "Unable to get signature by running '%@'.\n" = "Kunde inte k\u00f6ra '%@' f\u00f6r att f\u00e5 signaturen.\n"; /* File: Pref_Posting.m:65 */ "Unable to read signature from '%@'.\n" = "Kunde inte l\u00e4sa signatur fr\u00e5n '%@'.\n"; /*** Strings from Pref_ReadAhead.m ***/ /* File: Pref_ReadAhead.m:181 */ "Next unread past the current thread." = "N\u00e4sta ol\u00e4sta inte i denna tr\u00e5den."; /* File: Pref_ReadAhead.m:188 */ "Next unread." = "N\u00e4sta ol\u00e4sta."; /* File: Pref_ReadAhead.m:202 */ "Next." = "N\u00e4sta."; /* File: Pref_ReadAhead.m:195 */ "Previous." = "F\u00e4reg\u00e5ende."; /* File: Pref_ReadAhead.m:216 */ "Read ahead (ie. start downloading related messages\nwhen a message is selected)." = "Read ahead (dvs. b\u00f6rja ladda ner relaterade meddelanden\nn\u00e4r ett meddelande v\u00e4ljs)."; /* File: Pref_ReadAhead.m:123 */ "Read-\nahead" = "Read-\nahead"; /* File: Pref_ReadAhead.m:118 */ "Read-ahead" = "Read-ahead"; /* File: Pref_ReadAhead.m:147 */ "Read-ahead downloads messages smaller than:" = "Read-ahead laddar ner meddelanden mindre \u00e4n:"; /*** Strings from Pref_Sources.m ***/ /* File: Pref_Sources.m:97 */ "Id" = "Id"; /* File: Pref_Sources.m:235 */ "Message sources" = "Meddelandek\u00e4llor"; /* File: Pref_Sources.m:218 */ "Message\nsources" = "Meddelande-\nk\u00e4llor"; /* File: Pref_Sources.m:158 */ "Properties..." = "Egenskaper..."; /* File: Pref_Sources.m:109 */ "Type" = "Typ"; /*** Strings from PreferencesWindowController.m ***/ /* File: PreferencesWindowController.m:118 */ "Preferences" = "Inst\u00e4llningar"; /* File: PreferencesWindowController.m:66 */ "Revert" = "\u00c5terst\u00e4ll"; /* File: PreferencesWindowController.m:74 */ "Save" = "Spara"; /*** Strings from main.m ***/ /* File: main.m:270 */ "All" = "Alla"; /* File: main.m:295 */ "Arrival" = "Ankomst"; /* File: main.m:264 */ "Branch" = "Gren"; /* File: main.m:363 */ "Close" = "St\u00e4ng"; /* File: main.m:334 */ "Compose" = "Skriv"; /* File: main.m:340 */ "Copy" = "Kopiera"; /* File: main.m:343 */ "Cut" = "Klipp"; /* File: main.m:349 */ "Edit" = "Redigera"; /* File: main.m:302 */ "Folder" = "Mapp"; /* File: main.m:331 */ "Followup to group" = "Uppf\u00f6ljning till grupp"; /* File: main.m:375 */ "Hide" = "G\u00f6m"; /* File: main.m:225 */ "Info" = "Info"; /* File: main.m:358 */ "Log window" = "Logg-f\u00f6nster"; /* File: main.m:273 */ "Mark read" = "Markera l\u00e4st"; /* File: main.m:323 */ "Message" = "Meddelande"; /* File: main.m:259 */ "Move to" = "Flytta till"; /* File: main.m:329 */ "New article" = "Ny artikel"; /* File: main.m:253 */ "Next branch" = "N\u00e4sta gren"; /* File: main.m:256 */ "Next thread" = "N\u00e4sta tr\u00e5d"; /* File: main.m:247 */ "Next unread" = "N\u00e4sta ol\u00e4sta"; /* File: main.m:233 */ "Open folder" = "\u00d6ppna mapp"; /* File: main.m:230 */ "Open list" = "\u00d6ppna lista"; /* File: main.m:244 */ "Parent" = "F\u00f6r\u00e4lder"; /* File: main.m:346 */ "Paste" = "Klistra"; /* File: main.m:221 */ "Preferences..." = "Inst\u00e4llningar..."; /* File: main.m:379 */ "Quit" = "Avsluta"; /* File: main.m:297 */ "Reverse arrival" = "Omv\u00e4nd ankomst"; /* File: main.m:280 */ "Reverse thread" = "Omv\u00e4nd tr\u00e5d"; /* File: main.m:250 */ "Scroll/next" = "Scrolla/n\u00e4sta"; /* File: main.m:371 */ "Services" = "Tj\u00e4nster"; /* File: main.m:316 */ "Show source" = "Visa k\u00e4lla"; /* File: main.m:299 */ "Sort by" = "Ordna efter"; /* File: main.m:313 */ "Switch font" = "V\u00e4xla typsnitt"; /* File: main.m:278 */ /* File: main.m:267 */ "Thread" = "Tr\u00e5d"; /* File: main.m:310 */ "Toggle read/unread" = "\u00c4ndra l\u00e4st/ol\u00e4st"; /* File: main.m:354 */ "Update" = "Updatera"; /* File: main.m:389 */ "Welcome to LuserNET.app v%s" = "V\u00e4lkommen till LuserNET.app v%s"; /* File: main.m:366 */ "Windows" = "F\u00f6nster"; /* File: main.m:390 */ "alt-f brings up the folder lists; alt-u checks for new messages" = "alt-f \u00f6ppnar mapp-listan; alt-u h\u00e4mtar nya meddelanden";