10-Jan-92 terry Re: peanut to walnut (more suggestions)
Weiser, May 26, 1992 10:38 pm PDT
Date: Fri, 10 Jan 92 11:48:46 PST
From: terry:PARC:Xerox
Subject: Re: peanut to walnut (more suggestions)
In-reply-to: "Your message of 09 Jan 92 20:45:43 PST"
To: Mark Weiser
Cc: Douglas B. Terry, willie-sue
Mark,

The PeanutAndBlackCherry package only allows one to move BlackCherry message to Peanut, not vice versa. Sigh. So, you will need to write some code. Here are the options that I can think of:
A. Build a BlackCherry log file from the Peanut messages. Then you can use XFerToWalnut to get the messages into Walnut. Unfortunately, the messages all go into the Active message set.
B. Use WalnutOps to add messages to a Walnut database directly. For example, you could call WalnutOps.GenerateUniqueMsgName, WalnutOps.CreateMsg, and then WalnutOps.AddMsg. (See WalnutMsgSetDisplayerImpl.DoAppendMsg for sample code.)
C. Build a Walnut log file from the Peanut messages. You will need to write log entries to create each message and move messages to the desired message sets (and possibly to create the desired message sets). I'm not sure off-hand what other log entries need to be there to make Walnut happy. You can call WalnutStream.WriteEntry to write the log entries. This is a much lower-level approach than option B, but has the advantage that you don't need Walnut running (you can simply run WalnutStreamImpl).
D. Build a Walnut archive log from the Peanut messages. Walnut has the ability to read and write archive logs (in "PARC standard archive mail format"). These have a much simpler format than the Walnut log itself. Each archive log entry contains the message text, an indication of the message set to which this message belongs, and some header info. When Walnut reads one of these archive logs, it places the messages in the proper message sets and even creates the message sets if necessary (I think). See /tilde/terry/ZZMsgSetNameZZ.ArchiveLog for a sample archive log. (The code that Walnut uses to write such files is in WalnutOpsInternalImpl.DoWriteArchiveFile.)

The format of an archive entry is basically as follows (where the length fields are fixed size):
*start*
length prefixLength US
@itemLength
00525 formatLength
msgID
msgSet

format
@message
Where:
length = prefixLen + textLen + 1
prefixLength = length of everything before the message header
textLen = length of message, i.e. everything after the second "@"
itemLength = length of everything between the two "@"s
formatLength = length of format
msgID = $ mail server name@date
msgSet = name of message set where this message goes
format = Tioga formatting (this can be empty)

None of these options seems too daunting. I think that I recommend option D. It has the advantage that you don't need Walnut running to generate the log file. You can probably write the program in whatever language you want. You debug it by making sure the generated file looks correct before handing it to Walnut. You can either write one log file for all of your Peanut mail or one per message set.

Good luck...

... Doug
  2-Mar-92 Doug Wyatt Re: do I have this right?
Weiser, March 7, 1992 6:33 pm PST
Date: 02 Mar 92 11:50:30 PST
From: Doug Wyatt:PARC:xerox
Subject: Re: do I have this right?
In-reply-to: "Your message of 01 Mar 92 22:49:56 PST"
To: Mark Weiser
cc: Doug Wyatt, spreitzer, atkinson
Oh, yuk. Viewers shouldn't need to be involved at all, if you're just reading and writing files, as I think you said. Or perhaps you've got a peanut mail file in a viewer, and you want to select some message and copy it to the walnut archive. In any case, there must be a better way.
PCedar2.0 doesn't export the bit of functionality you want (this is fixed in Cedar10, naturally), but it's just a few lines, adapted from TEditDocumentsImpl.GetTEditDocument:
GetContents: PROC [root: TextNode.Ref] RETURNS [text, formatting: ROPE] ~ {
dataLen, count: INT; rope: ROPE;
[dataLen, count, rope] ← PutGet.ToRope[root];
RETURN[
text: Rope.Substr[base: rope, start: 0, len: dataLen],
formatting: Rope.Substr[base: rope, start: dataLen, len: count-dataLen]
];
};
The other awkwardness is that PutGet.ToRope can only be applied to an entire document, so to get the text and formatting for part of a larger document you have to copy the part. For the current selection, try TiogaAccessExtras.CopySelection[].textNode. Otherwise, maybe you can use EditSpanSupport.CopySpan.
Does this help? Feel free to reply with any further questions.
-- Doug
  3-Mar-92 Doug Wyatt Re: TextNode.Ref vs. TiogaOpsDefs.Ref
Date: 03 Mar 92 11:22:46 PST
From: Doug Wyatt:PARC:xerox
Subject: Re: TextNode.Ref vs. TiogaOpsDefs.Ref
In-reply-to: "Mark Weiser's message of 02 Mar 92 17:16:59 PST"
To: Mark Weiser
cc: Doug Wyatt
Indeed they both point to a Tioga node, but TiogaOpsDefs.Ref points to an opaque representation. To use both in a module, have the module EXPORT TiogaOpsDefs and include the declaration:
NodeBody: PUBLIC TYPE = TextNode.Body;
This declares that your module knows the concrete representation of TiogaOpsDefs.NodeBody. Then always use the concrete type (TextNode.Ref) in your code. There are some quirks in the opaque type mechanism, but this technique works in most situations.
(We removed this opaque type in Cedar10; it wasn't particularly useful, and it causes just this sort of headache.)
-- D.
-------------------
Date: 11 Mar 92 11:11:43 PST
From: Doug Wyatt:PARC:xerox
Subject: Re: question (TiogaToWalnut)
In-reply-to: "weiser's message of Wed, 11 Mar 92 02:47:21 PST"
To: weiser
cc: wyatt, chauser, terry, atkinson, plass
Well, that was a puzzler, but I think I get it. The problem is with TiogaOps.NodeSearch: despite the claim of the comment in the interface, it depends in a subtle way on the state of the current primary selection! TiogaOps2Impl.NodeSearch calls TiogaOps2Impl.DoSearch, which calls TEditInputOpsImpl.CallWithLocks, which fails to call proc if SelectionRoot[pSel] is NIL. Saving a viewer kills the primary selection, and all your subsequent searches fail.
It's safer in general to stay away from TiogaOps and use lower-level Tioga interfaces. (This would be cleaner in Cedar10; in particular, I've overhauled the searching stuff.) Try something like this:
Node: TYPE ~ TextNode.Ref;
Location: TYPE ~ TextNode.Location;
Pattern: TYPE ~ TreeFind.Finder;
CreatePattern: PROC [target: ROPE, word: BOOLFALSE] RETURNS [Pattern] ~ {
RETURN [TreeFind.CreateFromRope[pattern: target, ignoreCase: TRUE, word: word]];
};
Search: PROC [pat: Pattern, first, last: Node] RETURNS [found: BOOL, loc: Location] ~ {
[found: found, where: loc.node, at: loc.where] ←
TreeFind.Try[finder: pat, first: first, last: last];
};
pat1: Pattern ~ CreatePattern[target: "\r$$Date:$$"];
pat2: Pattern ~ CreatePattern[target: "\n$$Date:$$"];
pat3: Pattern ~ CreatePattern[target: "|$$Date:$$"];
pat4: Pattern ~ CreatePattern[target: "$$Date:$$", word: TRUE];
ExtractDate: PUBLIC PROC [ref: Node] RETURNS [date: ROPE] ~ {
found1, found2, found3, found4: BOOL;
foundnode, end1, end2, end3, end4: Location;
errorloc: Location;
textWithDate, errorField: ROPE;
endOfDate: INT;
{
last: Node ~ TextNode.LastWithin[ref];
[found: found1, loc: end1] ← Search[pat1, ref, last];
[found: found2, loc: end2] ← Search[pat2, ref, last];
[found: found3, loc: end3] ← Search[pat3, ref, last];
[found: found4, loc: end4] ← Search[pat4, ref, last];
... etc. ...