fill /acme

This commit is contained in:
cinap_lenrek 2011-04-14 17:27:24 +00:00
parent 71cfa9c637
commit a150899221
114 changed files with 14220 additions and 0 deletions

View File

6
acme/acid/Acid Executable file
View File

@ -0,0 +1,6 @@
#!/bin/rc
if(~ $#* 0){
echo usage: Acid pid >[2=1]
exit usage
}
win acid -l acme $*

3
acme/acid/guide Normal file
View File

@ -0,0 +1,3 @@
broke|rc kill program|rc
Acid pid
Acid -l thread -l acidfile pid

30
acme/bin/Battery Executable file
View File

@ -0,0 +1,30 @@
#!/bin/rc
if(! test -f /mnt/apm/battery){
echo no apm >[1=2]
exit 'no apm'
}
cd /mnt/acme/new
echo name /dev/apm >ctl
echo dump Battery >ctl
awkscript='
NR==1 {
if($3 != -1)
printf("%d%% %d:%02d %s", $2, $3/3600, ($3/60)%60, $1);
else
printf("%d%% %s", $2, $1);
}
'
fn chk {
what=`{awk $awkscript /mnt/apm/battery}
echo cleartag >ctl || exit die
echo clean >ctl || exit die
echo ' '^$"what >tag || exit die
}
chk
while(sleep 60)
chk

13
acme/bin/Isspam Executable file
View File

@ -0,0 +1,13 @@
#!/bin/rc
if(! ~ $#* 0){
echo usage: Isspam >[1=2]
exit usage
}
if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){
echo must run in mail directory >[1=2]
exit 'bad dir'
}
cat unixheader raw | upas/isspam

9
acme/bin/Mail Executable file
View File

@ -0,0 +1,9 @@
#!/bin/rc
#/mail/fs is read-protected unless fs is mounted
test -r /mail/fs || {
if(test -d /mnt/term/mail/fs/mbox) bind /mnt/term/mail/fs /mail/fs
if not upas/fs
}
exec /acme/bin/$objtype/Mail $*

7
acme/bin/Perl Executable file
View File

@ -0,0 +1,7 @@
#!/bin/rc
# aperl:
# Executes perl command and alters stderr to produce Acme-friendly error messages
# Created 02-JUL-1996, Luther Huffman, lutherh@stratcom.com
/bin/perl $* |[2] /bin/perl -pe 's/ line (\d+)/:$1 /' >[1=2]

3
acme/bin/README Normal file
View File

@ -0,0 +1,3 @@
The source directory should be called ./src instead of ./source,
but this directory is bound into /bin and there is a command called
src that the local directory would hide.

13
acme/bin/Spam Executable file
View File

@ -0,0 +1,13 @@
#!/bin/rc
if(! ~ $#* 0){
echo usage: Spam >[1=2]
exit usage
}
if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){
echo must run in mail directory >[1=2]
exit 'bad dir'
}
cat unixheader raw | upas/spam

13
acme/bin/Unspam Executable file
View File

@ -0,0 +1,13 @@
#!/bin/rc
if(! ~ $#* 0){
echo usage: Unspam >[1=2]
exit usage
}
if(! ~ `{pwd} /mail/fs/*/[0-9]* || ! test -f raw || ! test -f unixheader){
echo must run in mail directory >[1=2]
exit 'bad dir'
}
cat unixheader raw | upas/unspam

24
acme/bin/adiff Executable file
View File

@ -0,0 +1,24 @@
#!/bin/rc
if(~ $#* 0 1){
echo >[1=2] usage: adiff file1 file2
echo >[1=2] or adiff file1 file2... dir
exit usage
}
dir = /mnt/wsys
if(! test -f $dir/cons)
dir = /mnt/term/$dir
id=`{cat $dir/new/ctl}
id=$id(1)
l=$1
r=$2
if (test -d $1) l=$1/`{basename $2}
if not if (test -d $2) r=$2/`{basename $1}
echo 'name '^`{pwd}^/-diff-^`{basename $l} > $dir/$id/ctl
diff $* | awk -v 'l='$l -v 'r='^$r '/^diff/ {l=$2; r=$3; next} /^[1-9]/ {sub("[acd]", " & " r ":"); sub("^", l ":", $0)}
{print $0}' > $dir/$id/body
echo clean > $dir/$id/ctl

3
acme/bin/agrep Executable file
View File

@ -0,0 +1,3 @@
#!/bin/rc
exec grep -n $* /dev/null

12
acme/bin/ap Executable file
View File

@ -0,0 +1,12 @@
#!/bin/rc
args=''
while(~ $1 -*) {
args=$args^' '^$1
shift 1
}
if (~ $#1 0)
sysname=alice
if not
sysname=$1
if (! test -f /n/$sysname/usr/spool/ap ) { 9fs $sysname }
eval exec /acme/bin/$cputype/apread $args $sysname

43
acme/bin/aspell Executable file
View File

@ -0,0 +1,43 @@
#!/bin/rc
spellflags=()
fflag=''
for(x){
switch($x){
case -[bcvx]
spellflags=($spellflags $x)
case -f
fflag=$x
case *
if(~ $fflag -f) {
spellflags=($spellflags -f $x)
fflag=''
}
if not args = ($args $x)
}
}
dir = /mnt/wsys
if(! test -f $dir/cons)
dir = /mnt/term/$dir
id=`{cat $dir/new/ctl}
id=$id(1)
if(~ $#args 1 && ~ $args /*){
adir = `{basename -d $args}
args = `{basename $args}
echo 'name '^$adir^/-spell > $dir/$id/ctl
cd $adir
}
if not {
echo 'name '^`{pwd}^/-spell > $dir/$id/ctl
}
{
echo noscroll
if(~ $#args 0)
/acme/bin/$cputype/spout | sort -t: -u +2 | sort -t: +1.1n | aux/sprog -a $spellflags > $dir/$id/body
if not for(i in $args)
/acme/bin/$cputype/spout $i | sort -t: -u +2 | sort -t: +1.1n | aux/sprog -a $spellflags > $dir/$id/body
echo clean
}> $dir/$id/ctl

4
acme/bin/guide Normal file
View File

@ -0,0 +1,4 @@
win
aspell file
adiff file1 file2
adict -d oed

3
acme/bin/ind Executable file
View File

@ -0,0 +1,3 @@
#!/bin/rc
sed 's/^/ /' $*

10
acme/bin/new Executable file
View File

@ -0,0 +1,10 @@
#!/bin/rc
id=`{cat /mnt/acme/new/ctl}
id=$id(1)
cmd = $*
if(~ $#cmd 0) cmd = cat
echo 'name '^`{pwd}^/-^`{basename $cmd(1)} > /mnt/acme/$id/ctl
$cmd > /mnt/acme/$id/body
echo clean > /mnt/acme/$id/ctl

3
acme/bin/quote Executable file
View File

@ -0,0 +1,3 @@
#!/bin/rc
sed 's/^/> /' $*

View File

@ -0,0 +1,33 @@
This is a CD player for use under Acme.
It is derived from my earlier cdplay, which
was in turn derived from a 2nd edition player
called vcd. I think hardly any of the code from
vcd is left anymore, but it's what got me started.
Vcd was originally by David Hogan with additions
by Alberto Nava. David Hogan claims the only
code left is the definition of struct Msf.
Run it by executing "acd /dev/sdD0", where
/dev/sdD0 is your CD reader.
A window with a track list will appear, with
tracks named Track 1, Track 2, etc.
If it can be found in the freedb.org CD database,
real track names will replace the boring
ones before long.
To start playing a track, right click the number.
A "> " marks the currently playing track.
When that track finishes, acd plays the track
on the next line. This means you can edit
the window as thought it were a play list.
If the next line is "repeat", acd will start again
at the first song listed in the window.
CD changes are handled gracefully.
Russ Cox
9 August 2000
rsc@plan9.bell-labs.com

243
acme/bin/source/acd/access Normal file
View File

@ -0,0 +1,243 @@
TWO FORMS OF ACCESS TO THE FREEDB
---------------------------------
In the following document we will refer to CDDB instead of freedb, since
from a technical point of view, freedb is a CDDB-Server as it uses the
CDDB-protocol.
If you are interested in incorporating the use of freedb in your
software, there are two forms of access that you may consider.
1. <a href="#local">Local access</a>
In this mode your software simply attempts to open local files on
the computer to access the CDDB.
2. <a href="#remote">Remote access</a>
In this mode the software must connect to a freedb server on the
network to access the CDDB. There is a CDDB server protocol that
the software (also known as the "client") must use to converse with
the server.
You may choose to support either one, or both of these modes.
CDDB DISCID
-----------
Both forms of CDDB access requires that the software computes a "disc
ID" which is an identifier that is used to access the CDDB. The disc
ID is a 8-digit hexadecimal (base-16) number, computed using data from
a CD's Table-of-Contents (TOC) in MSF (Minute Second Frame) form. The
algorithm is listed below in the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=6">DISCID Howto</a>.
It is crucial that your software compute the disc ID correctly. If it
does not generate the disc ID, it will not be compatible with the
CDDB. Moreover, if your software submits CDDB entries with bad disc
IDs to the freedb archives, it could compromise the integrity of the
freedb.
If you have access to a UNIX platform that xmcd supports, we suggest
installing xmcd, and then test the disc ID code in your software by
comparing the disc ID generated by xmcd with that of your software,
for as large a number of CDs as possible.
<a name="local"></a>LOCAL CDDB ACCESS
-----------------
There are two forms of the CDDB archive available, the standard form
and the alternate form. Both forms are available for download from
various servers. You can always find an actual list of mirrors on the
freedb-homepage at <a href="http://freedb.freedb.org">http://freedb.freedb.org</a>.
The standard form of the CDDB archive is released to the public as
a UNIX tar(1)-format archive, compressed with gzip. The alternate
form archive is in the .zip format that is popular on the Windows
platform.
Standard Form:
--------------
Each CD entry is a separate file in the xmcd CDDB. These files are
organized in several directories, each directory is a category of
music. Currently the "official" categories are listed as follows:
blues
classical
country
data
folk
jazz
misc
newage
reggae
rock
soundtrack
The individual CDDB files have a file name that is the 8-digit disc
ID. For example, under the blues directory there may be the following
files:
0511c012
060e7314
0c01e902
0f0c3112
...
fa0f6f10
fb0f8814
fd0e6013
To access the CDDB entry associated with a CD, your software simply
opens the appropriate file and reads the information.
The content of each of these files is in a format described in the
<a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>.
Different pressings of a particular CD title may contain differences
in timings that can cause the computed disc ID to be different.
The CDDB allows this by having multiple file names be links to
the same file. The links are implemented as actual filesystem links
(see the ln(1) command) on UNIX systems. For example, the following
files in the rock directory are all links to the same file, and
refer to the CD "Pink Floyd / The Division Bell".:
850f740b
850f950b
850f970b
860f960b
890f970b
Xmcd and the CD database server use this form of the CDDB archive. The
benefit of the standard form of the CDDB archive is very fast access,
and ease of add/delete/edit operations on entries.
Alternate Form:
---------------
Due to limitations in the FAT file system used on Windows 9x and
Windows ME, it is unfeasible to use the standard format CDDB archive
due to the large number of files. This is because such a filesystem
operates on fixed-size clusters and even a small file (and most CDDB
files are 1KB or less) would consume the space of a full cluster
(Depending upon disk size, a cluster can range from 4KB to 32KB in
size). Thus, a tremendous amount of disk space would be wasted on
these systems if the CDDB archive is used in its standard form.
An alternate form of the CDDB archives was created for use by software
that must operate on a system with the FAT limitations.
The alterate form still use the separate category directories as the
standard form, but concatenates many files into a smaller number of
files under each category. The first two digits of the CDDB file names
is used as a key for concatenation, each file is allowed to grow to
approximately 64KB in size before a new file is started. The file name
indicates what range of the digits are included in that file. For
example, under the blues category we may have the following files:
01to36
37to55
56to71
...
b2tod7
d8toff
The 01to36 file contains all CDDB entries with disc ID 01xxxxxx,
02xxxxxx, 03xxxxxx and so on, up to 36xxxxxx.
Each entry in the concatenated file begins with the keyword
#FILENAME=xxxxxxxx
where discid is the 8-digit hexadecimal disc ID of that entry. Your
software must search through the appropriate file to locate the desired
entry. The CDDB entry is in the format described in Appendix B below.
The alternate form avoids the problem of inefficient disk space
utilization on FAT-based filesystems, but is slower to access than the
standard form, and it is much more cumbersome to perform add/delete/edit
operations on a CDDB entry.
<a name="remote"></a>REMOTE CDDB ACCESS
------------------
Your software must be able to communicate with a remote CD server
system via TCP/IP or HTTP.
There are a number of public freedb servers operating
on the Internet. The <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=9">current list of public servers</a> is listed on the
freedb web page at:
http://freedb.freedb.org.
It may also be obtained programmatically via the CDDB protocol "sites"
command.
TCP/IP access:
All current freedb servers answer at TCP port 888. There may be future
sites that deviate from this convention, however.
HTTP access:
The freedb-servers can be accessed via the cddb.cgi. This is resides at the
following path: /~cddb/cddb.cgi
Thus, the URL for accessing the server at freedb.freedb.org is:
http://freedb.freedb.org/~cddb/cddb.cgi
You should make the freedb server host (or hosts) and port numbers
user-configurable in your software. Do not hard-wire the list of
CD database servers into your code. The list of active servers changes
over time.
The CDDB server protocol is described in the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=28">CDDB-protocol documentation</a>.
The CDDB entry returned from the server via a "cddb read" command is in
the format described <a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>.
You may experiment with the freedb server by connecting to port 888 of
the server host via the "telnet" program, and then typing the cddb
protocol commands by hand. For example:
telnet freedb.freedb.org 888
connects you to the freedb server at freedb.freedb.org.
Some additional notes for accessing freedb over the Internet:
Your application should always specify the highest documented protocol
level. The highest level currently supported is "3". Lower protocol
levels will work, but are only provided for compatibility with older
CDDB applications. If you do not use the highest available protocol
level, certain important features will not be available to your
application.
Make sure to use the proper arguments with the "hello" command. The user
and hostname arguments should be that of the user's email address, not
some fixed hard-coded value. The application name and version should be
that of your application, not that of another existing application.
We consider the use of the "cddb query" command mandatory for all CDDB
clients. It is not valid to issue a "cddb read" command without issuing
a prior "cddb query" and receiving a good response, as it may yield incorrect
results. In addition, it is clients should support close matches
(aka "fuzzy" matches, or response code 211).
The proper way to handle multiple fuzzy matches is to present the
entire list of matches to the user and to let the user choose between them.
Matches are listed in the order of best fit for the user's disc, so they
should be presented to the user in the order they are listed by the server.
The suggested algorithm for obtaining the list of server sites is
as follows. The application should offer to get the list from
freedb.freedb.org with the "sites" command the first time the user runs
the program. Additionally the application should provide the user with
some method of downloading the list on-demand.
We do strongly suggest that you provide your users with the capability of
choosing freedb server sites as described above. However, for some
applications this may not be feasible. If you do not wish to offer this
functionality, you may safely hard-code "freedb.freedb.org" in your
application as the sole freedb site to access. This will deprive your users
of the option to choose a site near their locale for optimal response, but
that is your choice.

171
acme/bin/source/acd/acd.h Normal file
View File

@ -0,0 +1,171 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <disk.h>
#include <auth.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
/* acme */
typedef struct Event Event;
typedef struct Window Window;
enum
{
STACK = 16384,
EVENTSIZE = 256,
NEVENT = 5,
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* file descriptors */
int ctl;
int event;
int addr;
int data;
Biobuf *body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int id;
int open;
Channel *cevent; /* chan(Event*) */
};
extern Window* newwindow(void);
extern int winopenfile(Window*, char*);
extern void winopenbody(Window*, int);
extern void winclosebody(Window*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern void winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winwritebody(Window*, char*, int);
extern void winclean(Window*);
extern int winselect(Window*, char*, int);
extern int winsetaddr(Window*, char*, int);
extern char* winreadbody(Window*, int*);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern char* readfile(char*, char*, int*);
extern void ctlprint(int, char*, ...);
extern void* emalloc(uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern int tokenizec(char*, char**, int, char*);
/* cd stuff */
typedef struct Msf Msf; /* minute, second, frame */
struct Msf {
int m;
int s;
int f;
};
typedef struct Track Track;
struct Track {
Msf start;
Msf end;
ulong bstart;
ulong bend;
char *title;
};
enum {
MTRACK = 64,
};
typedef struct Toc Toc;
struct Toc {
int ntrack;
int nchange;
int changetime;
int track0;
Track track[MTRACK];
char *title;
};
extern int msfconv(Fmt*);
#pragma varargck argpos error 1
#pragma varargck argpos ctlprint 2
#pragma varargck type "M" Msf
enum { /* state */
Sunknown,
Splaying,
Spaused,
Scompleted,
Serror,
};
typedef struct Cdstatus Cdstatus;
struct Cdstatus {
int state;
int track;
int index;
Msf abs;
Msf rel;
};
typedef struct Drive Drive;
struct Drive {
Window *w;
Channel *cstatus; /* chan(Cdstatus) */
Channel *ctocdisp; /* chan(Toc) */
Channel *cdbreq; /* chan(Toc) */
Channel *cdbreply; /* chan(Toc) */
Scsi *scsi;
Toc toc;
Cdstatus status;
};
int gettoc(Scsi*, Toc*);
void drawtoc(Window*, Drive*, Toc*);
void redrawtoc(Window*, Toc*);
void tocproc(void*); /* Drive* */
void cddbproc(void*); /* Drive* */
void cdstatusproc(void*); /* Drive* */
extern int debug;
#define DPRINT if(debug)fprint
void acmeevent(Drive*, Window*, Event*);
int playtrack(Drive*, int, int);
int pause(Drive*);
int resume(Drive*);
int stop(Drive*);
int eject(Drive*);
int ingest(Drive*);
int markplay(Window*, ulong);
int setplaytime(Window*, char*);
void advancetrack(Drive*, Window*);

347
acme/bin/source/acd/acme.c Normal file
View File

@ -0,0 +1,347 @@
#include "acd.h"
static int
iscmd(char *s, char *cmd)
{
int len;
len = strlen(cmd);
return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
}
static char*
skip(char *s, char *cmd)
{
s += strlen(cmd);
while(*s==' ' || *s=='\t' || *s=='\n')
s++;
return s;
}
//#define PLAYSTRING "/^[0-9:]+>"
//#define PLAYSTRINGSPACE "/^[0-9:]+> ?"
//#define INITSTRING "0:00> "
#define INITSTRING "> "
#define PLAYSTRING "/^>"
#define PLAYSTRINGSPACE "/^> ?"
/*
* find the playing string, leave in addr
* if q0, q1 are non-nil, set them to the addr of the string.
*/
int
findplay(Window *w, char *s, ulong *q0, ulong *q1)
{
char xbuf[25];
if(w->data < 0)
w->data = winopenfile(w, "data");
if(!winsetaddr(w, "#0", 1) || !winsetaddr(w, s, 1))
return 0;
seek(w->addr, 0, 0);
if(read(w->addr, xbuf, 24) != 24)
return 0;
xbuf[24] = 0;
if(q0)
*q0 = atoi(xbuf);
if(q1)
*q1 = atoi(xbuf+12);
return 1;
}
/*
* find the playing string and replace the time
*/
int
setplaytime(Window *w, char *new)
{
char buf[40];
ulong q0, q1;
return 1;
if(!findplay(w, PLAYSTRING, &q0, &q1))
return 0;
q1--; /* > */
sprint(buf, "#%lud,#%lud", q0, q1);
DPRINT(2, "setaddr %s\n", buf);
if(!winsetaddr(w, buf, 1))
return 0;
if(write(w->data, new, strlen(new)) != strlen(new))
return 0;
return 1;
}
/*
* find the playing string, and remove it.
* return the string at the beginning of hte next line in buf
* (presumably a track number).
*/
static int
unmarkplay(Window *w, char *buf, int n, ulong *q0, ulong *q1, ulong *qbegin)
{
char xbuf[24];
if(!findplay(w, PLAYSTRINGSPACE, q0, q1))
return 0;
if(write(w->data, "", 0) < 0 || !winsetaddr(w, "+1+#0", 1))
return 0;
if(qbegin) {
seek(w->addr, 0, 0);
if(read(w->addr, xbuf, 24) != 24)
return 0;
*qbegin = atoi(xbuf);
}
if(buf) {
if((n = read(w->data, buf, n-1)) < 0)
return 0;
buf[n] = '\0';
}
return 1;
}
int
markplay(Window *w, ulong q0)
{
char buf[20];
if(w->data < 0)
w->data = winopenfile(w, "data");
sprint(buf, "#%lud", q0);
DPRINT(2, "addr %s\n", buf);
if(!winsetaddr(w, buf, 1) || !winsetaddr(w, "-0", 1))
return 0;
if(write(w->data, INITSTRING, strlen(INITSTRING)) != strlen(INITSTRING))
return 0;
return 1;
}
/* return 1 if handled, 0 otherwise */
int
cdcommand(Window *w, Drive *d, char *s)
{
s = skip(s, "");
if(iscmd(s, "Del")){
if(windel(w, 0))
threadexitsall(nil);
return 1;
}
if(iscmd(s, "Stop")){
unmarkplay(w, nil, 0, nil, nil, nil);
stop(d);
return 1;
}
if(iscmd(s, "Eject")){
unmarkplay(w, nil, 0, nil, nil, nil);
eject(d);
return 1;
}
if(iscmd(s, "Ingest")){
unmarkplay(w, nil, 0, nil, nil, nil);
ingest(d);
return 1;
}
if(iscmd(s, "Pause")){
pause(d);
return 1;
}
if(iscmd(s, "Resume")){
resume(d);
return 1;
}
return 0;
}
void
drawtoc(Window *w, Drive *d, Toc *t)
{
int i, playing;
if(w->data < 0)
w->data = winopenfile(w, "data");
if(!winsetaddr(w, ",", 1))
return;
fprint(w->data, "Title\n\n");
playing = -1;
if(d->status.state == Splaying || d->status.state == Spaused)
playing = d->status.track-t->track0;
for(i=0; i<t->ntrack; i++)
fprint(w->data, "%s%d/ Track %d\n", i==playing ? "> " : "", i+1, i+1);
fprint(w->data, "");
}
void
redrawtoc(Window *w, Toc *t)
{
int i;
char old[50];
if(w->data < 0)
w->data = winopenfile(w, "data");
if(t->title) {
if(winsetaddr(w, "/Title", 1))
write(w->data, t->title, strlen(t->title));
}
for(i=0; i<t->ntrack; i++) {
if(t->track[i].title) {
sprint(old, "/Track %d", i+1);
if(winsetaddr(w, old, 1))
write(w->data, t->track[i].title, strlen(t->track[i].title));
}
}
}
void
advancetrack(Drive *d, Window *w)
{
int n;
ulong q0, q1, qnext;
char buf[20];
q0 = q1 = 0;
if(!unmarkplay(w, buf, sizeof(buf), &q0, &q1, &qnext)) {
DPRINT(2, "unmark: %r\n");
return;
}
DPRINT(2, "buf: %s\n", buf);
if(strncmp(buf, "repeat", 6) == 0) {
if(!winsetaddr(w, "#0", 1) || !findplay(w, "/^[0-9]+\\/", &qnext, nil)) {
DPRINT(2, "set/find: %r\n");
return;
}
if(w->data < 0)
w->data = winopenfile(w, "data");
if((n = read(w->data, buf, sizeof(buf)-1)) <= 0) {
DPRINT(2, "read %d: %r\n", n);
return;
}
buf[n] = 0;
DPRINT(2, "buf: %s\n", buf);
}
if((n = atoi(buf)) == 0)
return;
if(!markplay(w, qnext))
DPRINT(2, "err: %r");
playtrack(d, n-1, n-1);
}
void
acmeevent(Drive *d, Window *w, Event *e)
{
Event *ea, *e2, *eq;
char *s, *t, *buf;
int n, na;
ulong q0, q1;
switch(e->c1){ /* origin of action */
default:
Unknown:
fprint(2, "unknown message %c%c\n", e->c1, e->c2);
break;
case 'E': /* write to body or tag; can't affect us */
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K': /* type away; we don't care */
break;
case 'M': /* mouse event */
switch(e->c2){ /* type of action */
case 'x': /* mouse: button 2 in tag */
case 'X': /* mouse: button 2 in body */
ea = nil;
// e2 = nil;
s = e->b;
if(e->flag & 2){ /* null string with non-null expansion */
e2 = recvp(w->cevent);
if(e->nb==0)
s = e2->b;
}
if(e->flag & 8){ /* chorded argument */
ea = recvp(w->cevent); /* argument */
na = ea->nb;
recvp(w->cevent); /* ignore origin */
}else
na = 0;
/* append chorded arguments */
if(na){
t = emalloc(strlen(s)+1+na+1);
sprint(t, "%s %s", s, ea->b);
s = t;
}
/* if it's a known command, do it */
/* if it's a long message, it can't be for us anyway */
DPRINT(2, "exec: %s\n", s);
if(!cdcommand(w, d, s)) /* send it back */
winwriteevent(w, e);
if(na)
free(s);
break;
case 'l': /* mouse: button 3 in tag */
case 'L': /* mouse: button 3 in body */
// buf = nil;
eq = e;
if(e->flag & 2){
e2 = recvp(w->cevent);
eq = e2;
}
s = eq->b;
if(eq->q1>eq->q0 && eq->nb==0){
buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
winread(w, eq->q0, eq->q1, buf);
s = buf;
}
DPRINT(2, "load %s\n", s);
if((n = atoi(s)) != 0) {
DPRINT(2, "mark %d\n", n);
q0 = q1 = 0;
unmarkplay(w, nil, 0, &q0, &q1, nil);
/* adjust eq->q* for deletion */
if(eq->q0 > q1) {
eq->q0 -= (q1-q0);
eq->q1 -= (q1-q0);
}
if(!markplay(w, eq->q0))
DPRINT(2, "err: %r\n");
playtrack(d, n-1, n-1);
} else
winwriteevent(w, e);
break;
case 'i': /* mouse: text inserted in tag */
case 'I': /* mouse: text inserted in body */
case 'd': /* mouse: text deleted from tag */
case 'D': /* mouse: text deleted from body */
break;
default:
goto Unknown;
}
}
}

206
acme/bin/source/acd/cddb Normal file
View File

@ -0,0 +1,206 @@
<html><html>
<head>
<title>::freedb.org::</title>
</head>
<body bgcolor="#FFFFFF" text="#000000" link="#101070" vlink="#101070">
<center>
<table cellpadding=0 cellspacing=0 border=0 width="99%" align=center><tr><td align=left>
<a href="/">
<table border=0>
<td bgcolor="#ffffff">
<table border=0 width=100% cellpadding=0 cellspacing=0>
<td bgcolor=#101070>
<table border=0>
<td bgcolor=#ffffff><font face="Arial,Helvetica,Lucida" color="#101070" size=8><b>freedb</b></td>
<td bgcolor=#101070><font face="Arial,Helvetica,Lucida" color="#ffffff" size=8><b>.org</b></td>
</table>
</td>
<tr>
<td align=right><font face="Arial,Helvetica,Lucida" color="#101070" size=2><b>a free approach to cddbp
</table>
</td>
</table>
</a>
</td><td align=right width=100%>
<form action="search.php" method=post>
<font face=Arial,Helvetica size=2><input type=name name=query width=20 size=20 length=20>
</td>
<td align=right>&nbsp;&nbsp;<input type=image src=images/menu/english/search.gif border=0 align=middle></td>
</form>
</td></tr></table><br>
<table cellpadding=0 cellspacing=0 border=0 width="99%" bgcolor=101070><tr><td>
<table cellpadding=5 cellspacing=1 border=0 width="100%" bgcolor=FFFFFF><tr><td>
<font face=Lucida,Verdana,Arial,Helvetica size=2>freedb.org - a free approach to cddbp</td></tr></table></td></tr></table><P>
<table width="99%" align=center cellpadding=0 cellspacing=0 border=0><tr>
<td valign=top rowspan=5>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Main Menu</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
<li><a href=index.php>Home</a>
<li><a href=topics.php>News-Topics</a>
<li><a href=sections.php?op=listarticles&secid=1>About</a>
<li><a href=sections.php?op=listarticles&secid=2>Developers</a>
<li><a href=sections.php?op=listarticles&secid=3>Applications</a>
<li><a href=sections.php?op=listarticles&secid=7>Download</a>
<li><a href=forum/index.php>Forum</a>
<li><a href=http://freedb.music.sk/search/>Web-based Search</a>
<li><a href=links.php>Web Links</a>
<li><a href=user.php>Your Account</a>
<li><a href=submit.php>Submit News</a>
</font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>FAQ</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
Our FAQ can be found <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=26">here</a>.<br>
Please read the FAQ before asking questions via email. </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Contact</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
General questions:<br>
<a href="mailto:info@freedb.org">info@freedb.org</a><hr>
Databaseupdates:<br>
<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>
(<b>NOT</b> for submission!)<hr>
Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>
Submits have to go to:<br>
<a href="mailto:freedb-submit@freedb.org">freedb-submit@freedb.org</a> </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Downloads</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
The link to the database downloads is <a href="/sections.php?op=viewarticle&artid=12">here</a> </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<td>&nbsp;</td><td valign="top" width="100%">
<!-- columna de inicio -->
<center>
<table border=0 cellpadding=1 cellspacing=0 width=100% bgcolor=000000><tr><td>
<table border=0 cellpadding=8 cellspacing=0 width=100% bgcolor=FFFFFF>
<tr><td align=left><font face=Arial,Helvetica size=3>
<b>Database-format specification</b><br>
<font size=2>
<br><br>
Due to problems with using backslashes on our Webpage, we cannot display the database-format specification directly here.<br>
But you can find it <a href="http://www.freedb.org/software/old/DBFORMAT">here</a> as a text-document.
</tr></td>
<tr><td align=center><font face=Arial,Helvetica>
&nbsp;
</tr></td>
</table></tr></td></table></center></td><td>&nbsp;</td>
</tr></table></td></tr></table><br><br>
<font face=Arial,Helvetica size=1><center>
<br>
<br>
<br>
<br>
</body>
</html>

197
acme/bin/source/acd/cddb.c Normal file
View File

@ -0,0 +1,197 @@
#include "acd.h"
#include <ctype.h>
/* see CDDBPROTO */
static ulong
cddb_sum(int n)
{
int ret;
ret = 0;
while(n > 0) {
ret += n%10;
n /= 10;
}
return ret;
}
static ulong
diskid(Toc *t)
{
int i, n, tmp;
Msf *ms, *me;
n = 0;
for(i=0; i < t->ntrack; i++)
n += cddb_sum(t->track[i].start.m*60+t->track[i].start.s);
ms = &t->track[0].start;
me = &t->track[t->ntrack].start;
tmp = (me->m*60+me->s) - (ms->m*60+ms->s);
/*
* the spec says n%0xFF rather than n&0xFF. it's unclear which is correct.
* most CDs are in the database under both entries.
*/
return ((n & 0xFF) << 24 | (tmp << 8) | t->ntrack);
}
static void
append(char **d, char *s)
{
char *r;
if (*d == nil)
*d = estrdup(s);
else {
r = emalloc(strlen(*d) + strlen(s) + 1);
strcpy(r, *d);
strcat(r, s);
free(*d);
*d = r;
}
}
static int
cddbfilltoc(Toc *t)
{
int fd;
int i;
char *p, *q;
Biobuf bin;
Msf *m;
char *f[10];
int nf;
char *id, *categ;
char gottrack[MTRACK];
int gottitle;
fd = dial("tcp!freedb.freedb.org!888", 0, 0, 0);
if(fd < 0) {
fprint(2, "cannot dial: %r\n");
return -1;
}
Binit(&bin, fd, OREAD);
if((p=Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2) {
died:
close(fd);
Bterm(&bin);
fprint(2, "error talking to server\n");
if(p) {
p[Blinelen(&bin)-1] = 0;
fprint(2, "server says: %s\n", p);
}
return -1;
}
fprint(fd, "cddb hello gre plan9 9cd 1.0\r\n");
if((p = Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2)
goto died;
fprint(fd, "cddb query %8.8lux %d", diskid(t), t->ntrack);
DPRINT(2, "cddb query %8.8lux %d", diskid(t), t->ntrack);
for(i=0; i<t->ntrack; i++) {
m = &t->track[i].start;
fprint(fd, " %d", (m->m*60+m->s)*75+m->f);
DPRINT(2, " %d", (m->m*60+m->s)*75+m->f);
}
m = &t->track[t->ntrack-1].end;
fprint(fd, " %d\r\n", m->m*60+m->s);
DPRINT(2, " %d\r\n", m->m*60+m->s);
if((p = Brdline(&bin, '\n')) == nil || atoi(p)/100 != 2)
goto died;
p[Blinelen(&bin)-1] = 0;
DPRINT(2, "cddb: %s\n", p);
nf = tokenize(p, f, nelem(f));
if(nf < 1)
goto died;
switch(atoi(f[0])) {
case 200: /* exact match */
if(nf < 3)
goto died;
categ = f[1];
id = f[2];
break;
case 211: /* close matches */
if((p = Brdline(&bin, '\n')) == nil)
goto died;
if(p[0] == '.') /* no close matches? */
goto died;
p[Blinelen(&bin)-1] = '\0';
/* accept first match */
nf = tokenize(p, f, nelem(f));
if(nf < 2)
goto died;
categ = f[0];
id = f[1];
/* snarf rest of buffer */
while(p[0] != '.') {
if((p = Brdline(&bin, '\n')) == nil)
goto died;
p[Blinelen(&bin)-1] = '\0';
DPRINT(2, "cddb: %s\n", p);
}
break;
case 202: /* no match */
default:
goto died;
}
/* fetch results for this cd */
fprint(fd, "cddb read %s %s\r\n", categ, id);
memset(gottrack, 0, sizeof(gottrack));
gottitle = 0;
do {
if((p = Brdline(&bin, '\n')) == nil)
goto died;
q = p+Blinelen(&bin)-1;
while(isspace(*q))
*q-- = 0;
DPRINT(2, "cddb %s\n", p);
if(strncmp(p, "DTITLE=", 7) == 0) {
if (gottitle)
append(&t->title, p + 7);
else
t->title = estrdup(p+7);
gottitle = 1;
} else if(strncmp(p, "TTITLE", 6) == 0 && isdigit(p[6])) {
i = atoi(p+6);
if(i < t->ntrack) {
p += 6;
while(isdigit(*p))
p++;
if(*p == '=')
p++;
if (gottrack[i])
append(&t->track[i].title, p);
else
t->track[i].title = estrdup(p);
gottrack[i] = 1;
}
}
} while(*p != '.');
fprint(fd, "quit\r\n");
close(fd);
Bterm(&bin);
return 0;
}
void
cddbproc(void *v)
{
Drive *d;
Toc t;
threadsetname("cddbproc");
d = v;
while(recv(d->cdbreq, &t))
if(cddbfilltoc(&t) == 0)
send(d->cdbreply, &t);
}

View File

@ -0,0 +1,894 @@
<html><html>
<head>
<title>::freedb.org::</title>
</head>
<body bgcolor="#FFFFFF" text="#000000" link="#101070" vlink="#101070">
<center>
<table cellpadding=0 cellspacing=0 border=0 width="99%" align=center><tr><td align=left>
<a href="/">
<table border=0>
<td bgcolor="#ffffff">
<table border=0 width=100% cellpadding=0 cellspacing=0>
<td bgcolor=#101070>
<table border=0>
<td bgcolor=#ffffff><font face="Arial,Helvetica,Lucida" color="#101070" size=8><b>freedb</b></td>
<td bgcolor=#101070><font face="Arial,Helvetica,Lucida" color="#ffffff" size=8><b>.org</b></td>
</table>
</td>
<tr>
<td align=right><font face="Arial,Helvetica,Lucida" color="#101070" size=2><b>a free approach to cddbp
</table>
</td>
</table>
</a>
</td><td align=right width=100%>
<form action="search.php" method=post>
<font face=Arial,Helvetica size=2><input type=name name=query width=20 size=20 length=20>
</td>
<td align=right>&nbsp;&nbsp;<input type=image src=images/menu/english/search.gif border=0 align=middle></td>
</form>
</td></tr></table><br>
<table cellpadding=0 cellspacing=0 border=0 width="99%" bgcolor=101070><tr><td>
<table cellpadding=5 cellspacing=1 border=0 width="100%" bgcolor=FFFFFF><tr><td>
<font face=Lucida,Verdana,Arial,Helvetica size=2>freedb.org - a free approach to cddbp</td></tr></table></td></tr></table><P>
<table width="99%" align=center cellpadding=0 cellspacing=0 border=0><tr>
<td valign=top rowspan=5>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Main Menu</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
<li><a href=index.php>Home</a>
<li><a href=topics.php>News-Topics</a>
<li><a href=sections.php?op=listarticles&secid=1>About</a>
<li><a href=sections.php?op=listarticles&secid=2>Developers</a>
<li><a href=sections.php?op=listarticles&secid=3>Applications</a>
<li><a href=sections.php?op=listarticles&secid=7>Download</a>
<li><a href=forum/index.php>Forum</a>
<li><a href=http://freedb.music.sk/search/>Web-based Search</a>
<li><a href=links.php>Web Links</a>
<li><a href=user.php>Your Account</a>
<li><a href=submit.php>Submit News</a>
</font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>FAQ</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
Our FAQ can be found <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=26">here</a>.<br>
Please read the FAQ before asking questions via email. </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Contact</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
General questions:<br>
<a href="mailto:info@freedb.org">info@freedb.org</a><hr>
Databaseupdates:<br>
<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>
(<b>NOT</b> for submission!)<hr>
Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>
Submits have to go to:<br>
<a href="mailto:freedb-submit@freedb.org">freedb-submit@freedb.org</a> </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Downloads</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
The link to the database downloads is <a href="/sections.php?op=viewarticle&artid=12">here</a> </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<td>&nbsp;</td><td valign="top" width="100%">
<!-- columna de inicio -->
<center>
<table border=0 cellpadding=1 cellspacing=0 width=100% bgcolor=000000><tr><td>
<table border=0 cellpadding=8 cellspacing=0 width=100% bgcolor=FFFFFF>
<tr><td align=left><font face=Arial,Helvetica size=3>
<b>CDDB-protocol documentation</b><br>
<font size=2>
<br><br>
<pre>
CDDB Protocol
By Steve Scherf and Ti Kan
--------------------------
Revision: $Id: CDDBPROTO,v 1.6 1997/05/14 07:53:52 steve Exp steve $
Notation:
-&gt; : client to server
&lt;- : server to client
terminating marker: `.' character in the beginning of a line
Server response code (three digit code):
First digit:
1xx Informative message
2xx Command OK
3xx Command OK so far, continue
4xx Command OK, but cannot be performed for some specified reasons
5xx Command unimplemented, incorrect, or program error
Second digit:
x0x Ready for further commands
x1x More server-to-client output follows (until terminating marker)
x2x More client-to-server input follows (until terminating marker)
x3x Connection will close
Third digit:
xx[0-9] Command-specific code
CDDB Protocol Level 1:
----------------------
Server sign-on banner:
----------------------
&lt;- code hostname CDDBP server version ready at date
code:
200 OK, read/write allowed
201 OK, read only
432 No connections allowed: permission denied
433 No connections allowed: X users allowed, Y currently active
434 No connections allowed: system load too high
hostname:
Server host name. Example: xyz.fubar.com
version:
Version number of server software. Example: v1.0PL0
date:
Current date and time. Example: Wed Mar 13 00:41:34 1996
Initial client-server handshake:
--------------------------------
Note: This handshake must occur before other cddb commands
are accepted by the server.
Client command:
-&gt; cddb hello username hostname clientname version
username:
Login name of user. Example: johndoe
hostname:
Host name of client. Example: abc.fubar.com
clientname:
The name of the connecting client. Example: xmcd, cda, EasyCD,
et cetera. Do not use the name of another client which already
exists.
version:
Version number of client software. Example: v1.0PL0
Server response:
&lt;- code hello and welcome username@hostname running clientname version
code:
200 Handshake successful
431 Handshake not successful, closing connection
402 Already shook hands
CDDB query:
-----------
Client command:
-&gt; cddb query discid ntrks off1 off2 ... nsecs
discid:
CD disc ID number. Example: f50a3b13
ntrks:
Total number of tracks on CD.
off1, off2, ...:
Frame offset of the starting location of each track.
nsecs:
Total playing length of CD in seconds.
Server response:
&lt;- code categ discid dtitle
or
&lt;- code close matches found
&lt;- categ discid dtitle
&lt;- categ discid dtitle
&lt;- (more matches...)
&lt;- .
code:
200 Found exact match
211 Found inexact matches, list follows (until terminating marker)
202 No match found
403 Database entry is corrupt
409 No handshake
categ:
CD category. Example: rock
discid:
CD disc ID number of the found entry. Example: f50a3b13
dtitle:
The Disc Artist and Disc Title (The DTITLE line). For example:
Pink Floyd / The Dark Side of the Moon
CDDB read:
----------
Client command:
-&gt; cddb read categ discid
categ:
CD category. Example: rock
discid:
CD disc ID number. Example: f50a3b13
Server response:
&lt;- code categ discid
&lt;- # xmcd 2.0 CD database file
&lt;- # ...
&lt;- (CDDB data...)
&lt;- .
or
&lt;- code categ discid No such CD entry in database
code:
210 OK, CDDB database entry follows (until terminating marker)
401 Specified CDDB entry not found.
402 Server error.
403 Database entry is corrupt.
409 No handshake.
categ:
CD category. Example: rock
discid:
CD disc ID number. Example: f50a3b13
CDDB search: (command not yet implemented in freedb-serversoftware!)
------------
Client command:
-&gt; cddb srch key search_type ... search_type
key:
Pseudo-regular expression to match. Expressions should meet the
following description:
- No white space.
- Printable characters only.
- Case is ignored.
search_type:
CDDB fields to search through. Example: title
Supported types: artist, title, extd, ext, trk
categ:
CD category. Example: rock
Server response:
&lt;- code matches found
&lt;- categ discid dtitle
&lt;- categ discid dtitle
&lt;- (more matches...)
&lt;- .
code:
210 OK, matches found, list follows (until terminating marker)
401 No match found.
409 No handshake.
categ:
CD category. Example: rock
dtitle:
The Disc Artist and Disc Title (The DTITLE line). For example:
Pink Floyd / The Dark Side of the Moon
CDDB write:
-----------
Client command:
-&gt; cddb write categ discid
categ:
CD category. Example: rock
discid:
CD disc ID number. Example: f50a3b13
Server response:
&lt;- code categ discid
code:
320 OK, input CDDB data (until terminating marker)
401 Permission denied.
402 Server file system full/file access failed.
409 No handshake.
501 Entry rejected: reason for rejection.
categ:
CD category. Example: rock
discid:
CD disc ID number. Example: f50a3b13
Client data:
-&gt; # xmcd 2.0 CD database file
-&gt; # ...
-&gt; (CDDB data)
-&gt; .
Server response:
&lt;- code message
code:
200 CDDB entry accepted
401 CDDB entry rejected: reason why
message:
Message string to indicate write status:
CDDB entry accepted, or CDDB entry rejected.
Help information:
-----------------
Client command:
-&gt; help
or
-&gt; help cmd
cmd:
CDDB command. Example: quit
or
-&gt; help cmd subcmd
cmd:
CDDB command. Example: cddb
subcmd:
CDDB command argument. Example: query
Server response:
&lt;- code Help information follows
&lt;- (help data ...)
&lt;- .
or
&lt;- code no help information available
code:
210 OK, help information follows (until terminating marker)
401 No help information available
Log statistics:
---------------
Client command:
-&gt; log [[-l lines] [start date [end date]] | [day [days]] | [get"]]
lines:
The maximum number of lines to print for each data list in the
log statistics.
start date:
The date after which statistics should be calculated. Date is
of the format: hh[mm[ss[MM[DD[[CC]YY]]]]]
E.g.: 201200053196 for 8:12 PM on May 31, 1996.
20120005312096 for 8:12 PM on May 31, 2096.
080530 for today at at 8:15 and 30 seconds.
If the century ("CC") is omitted, a reasonable guess is made. If
this argument is omitted, all messages are considered.
end date:
The date after which statistics should not be calculated. If
omitted, the end date is assumed to be the current date.
day:
The string "day". This solitary argument will cause a log search
of messages generated within the last day.
days:
A positive numerical argument which modifies the number of days'
messages to searh. If this argument is left out, the default is 1.
get:
The string "get". This solitary argument will cause the server
to send the contents of the log file.
Server response:
&lt;- code Log summary follows
&lt;- (log stats)
&lt;- .
or
&lt;- code Log follows
&lt;- (log stats)
&lt;- .
code:
210 OK, log summary follows (until terminating marker)
211 OK, log follows (until terminating marker)
401 Permission denied
402 No log information available
501 Invalid start/end date
Message of the day:
------------------
Client command:
-&gt; motd
Server response:
&lt;- code Last modified: date MOTD follows (until terminating marker)
&lt;- (message text)
&lt;- .
code:
210 Last modified: 05/31/96 06:31:14 MOTD follows (until terminating marker)
401 No message of the day available
date:
The date the text of the message of the day was modified. The date
appears in the following format:
05/31/96 06:31:14
This value may be used by client software as a message timestamp
for purposes of determining if it has already been displayed. This
format was chosen because it is more easily parsed than the standard
ctime() format.
Server protocol level:
----------------------
Client command:
-&gt; proto [level]
level:
The (numerical) protocol level to set the server to.
Server response:
&lt;- code CDDB protocol level: current cur_level, supported supported_level
or
&lt;- code OK, protocol version now: cur_level
code:
200 CDDB protocol level: current cur_level, supported supp_level
201 OK, protocol version now: cur_level
501 Illegal protocol level.
502 Protocol level already cur_level.
cur_level:
The current protocol level at which the server is running.
supported_level:
The maximum supported protocol level.
Server sites:
--------------
Client command:
-&gt; sites
Server response:
&lt;- code OK, site information follows (until terminating `.')
&lt;- (data)
&lt;- .
code:
210 Ok, site information follows
401 No site information available.
The data format is as follows:
site port latitude longitude description
The fields are as follows:
site:
The Internet address of the remote site.
port:
The port at which the server resides on that site.
latitude:
The latitude of the server site. The format is as follows:
CDDD.MM
Where "C" is the compass direction (N, S), "DDD" is the
degrees, and "MM" is the minutes.
longitude:
The longitude of the server site. Format is as above, except
the compass direction must be one of (E, W).
description:
A short description of the geographical location of the site.
Example:
cddb.moonsoft.com 888 N037.23 W122.01 Fremont, CA USA
Server status:
--------------
Client command:
-&gt; stat
Server response:
&lt;- code OK, status information follows (until terminating `.')
&lt;- (data)
&lt;- .
code:
210 Ok, status information follows
The possible data is as follows:
current proto: &lt;current_level&gt;
An integer representing the server's current operating protocol
level.
max proto: &lt;max_level&gt;
The maximum supported protocol level.
gets: &lt;yes | no&gt;
Whether or not the client is allowed to get log information,
according to the string "yes" or "no".
updates: &lt;yes | no&gt;
Whether or not the client is allowed to initiate a database
update, according to the string "yes" or "no".
posting: &lt;yes | no&gt;
Whether or not the client is allowed to post new entries,
according to the string "yes" or "no".
quotes: &lt;yes | no&gt;
Whether or not quoted arguments are enabled, according to
the string "yes" or "no".
current users: &lt;num_users&gt;
The number of users currently connected to the server.
max users: &lt;num_max_users&gt;
The number of users that can concurrently connect to the server.
strip ext: &lt;yes | no&gt;
Whether or not extended data is stripped by the server before
presented to the user.
Database entries: &lt;num_db_entries&gt;
The total number of entries in the database.
Database entries by category:
This field is followed by a list of catgories and the number
of entries in that category. Each entry is of the following
format:
&lt;white space&gt;catgory: &lt;num_db_entries&gt;
The list of entries is terminated by the first line that does
not begin with white space.
Pending file transmissions:
This field is followed by a list of sites that are fed new
database entries at periodic intervals, and the number of
entries that have yet to be transmitted to that site.
Each entry is of the following format:
&lt;white space&gt;site: &lt;num_db_entries&gt;
The list of entries is terminated by the first line that does
not begin with white space.
This list may grow as needed, so clients must expect possible
unrecognizable data. Also, additional fields may be added to
the currently existing lines, although no existing fields will
be removed or change position.
Server version:
---------------
Client command:
-&gt; ver
Server response:
&lt;- code servername version copyright
or
&lt;- code Version information follows
code:
200 Version information.
211 OK, version information follows (until terminating marker)
version:
Server version. Example: v1.0PL0
copyright:
Copyright string. Example: Copyright (c) 1996 Steve Scherf
Database update:
----------------
Client command:
-&gt; update
Server response:
&lt;- code Updating the database.
or
&lt;- code Permission denied.
or
&lt;- code Unable to update the database.
code:
200 Updating the database.
401 Permission denied.
402 Unable to update the database.
Server users:
-------------
Client command:
-&gt; whom
Server response:
&lt;- code User list follows
code:
210 OK, user list follows (until terminating marker)
401 No user information available.
Client sign-off:
----------------
Client command:
-&gt; quit
Server response:
&lt;- code hostname closing connection. Goodbye.
code:
230 OK, goodbye.
hostname:
Server host name. Example: xyz.fubar.com
General errors:
---------------
Server response:
&lt;- code error
code:
402 Server error.
408 CGI environment error.
500 Command syntax error, command unknown, command unimplemented.
530 Server error, server timeout.
Reserved errors:
----------------
The following error codes are reserved, and will never be returned as a
response to a CDDB protocol command. They are intended to be used internally
by clients that have a need for generating pseudo-responses.
600-699
CDDB Protocol Level 2:
----------------------
In all respects, protocol level 2 is the same as level 1, with the exceptions
listed below.
Arguments to commands may be surrounded by double quotes. All characters
within the quotes, including white space, are included in the argument. All
white space is replaced by the `_' (2Dh) character by the server. White space
is defined as ` ' (20h) and `^I' (control-I, or 09h).
Arguments containing quotes that should not be interpreted with the special
meaning described above should be escaped with a preceding backslash character,
or '' (5Ch). If an actual backslash appears in an argument, it should be
escaped with a preceding backslash. In both cases, the preceding backslash
will be removed from the input before being interpreted.
CDDB Protocol Level 3:
----------------------
Protocol level 3 is the same as level 2, with the exception listed below.
The output of the "sites" command has changed to meet the folowing description:
The data format is as follows:
site protocol port address latitude longitude description
The fields are as follows:
site:
The Internet address of the remote site.
protocol:
The transfer protocol used to access the site.
port:
The port at which the server resides on that site.
address:
Any additional addressing information needed to access the
server. For example, for HTTP protocol servers, this would be
the path to the CDDB server CGI script. This field will be
"-" if no additional addressing information is needed.
latitude:
The latitude of the server site. The format is as follows:
CDDD.MM
Where "C" is the compass direction (N, S), "DDD" is the
degrees, and "MM" is the minutes.
longitude:
The longitude of the server site. Format is as above, except
the compass direction must be one of (E, W).
description:
A short description of the geographical location of the site.
Example:
cddb.moonsoft.com cddbp 888 - N037.23 W122.01 Fremont, CA USA
cddb.moonsoft.com http 80 /~cddb/cddb.cgi N037.23 W122.01 Fremont,CA USA
Note that a site may appear once for each type of protocol it supports for
accessing the server.
Addendum A: Proper use of CDDBP:
--------------------------------
There are a few guidelines that must be followed in order to make proper use
of CDDBP:
- When handshaking with the server via the "cddb hello" command, the client
must specify its own name and version, not that of some other client (such
as xmcd). Also, the "username" and "hostname" must be that of the actual
user running the program, not some hardwired value.
- Before performing a "cddb read", the client program MUST perform a
"cddb query". Failure to do so may result in the client program receiving
incorrect CDDB data from the server. Also, without performing a query, the
client program will not benefit from close matches in the event of the
lack of an exact match in the database.
- For accounting purposes, it is best if client programs only perform a single
"cddb query" for a particular disc before performing a "cddb read" for that
disc.
Addendum B: CDDBP under HTTP:
-----------------------------
Accessing a server as a CGI script is done in much the same way as through
direct interaction. The command set is identical, though the method of
communication is through CDDBP commands encapsulated in the HTTP protocol.
The only limitation is that a single command may be executed per connection,
since HTTP is not truly interactive. For the server to be accessed in this
way, it must reside on the target host at a known URL which is accessible by
the host HTTP server. The client program must connect to the HTTP server on
the target host and issue an HTTP command with the appropriate CDDBP command
encapsulated within.
Commands may be submitted to servers in CGI mode using either the "GET" or
"POST" HTTP commands. Both methods are supported, and there is no real
difference between how both are to be used other than the syntactical
difference between the two methods. The "POST" method may provide the ability
to issue longer commands, though, depending on the architecture of the system
on which the server resides.
The server command must be sent as part of the "Request-URI" in the case
of the "GET" method, and as the "Entity-Body" in the case of the "POST"
method. In both cases, the command must be of the following form:
cmd=server+command&amp;hello=joe+my.host.com+clientname+version&amp;proto=1
Where the text following the "cmd=" represents the CDDBP command to be
executed, the text following the "hello=" represents the arguments to
the "cddb hello" command that is implied by this operation, and the
text following the "proto=" represents the argument to the "proto" command
that is implied by this operation.
The "+" characters in the input represent spaces, and will be translated
by the server before performing the request. Special characters may be
represented by the sequence "%XX" where "XX" is a two-digit hex number
corresponding to the ASCII (ISO-8859-1) sequence of that character. The
"&amp;" characters denote separations between the command, hello and proto
arguments. Newlines and carriage returns must not appear anywhere in the
string except at the end.
All CDDBP commands are supported under HTTP, except for "cddb hello",
"cddb write", "proto" and "quit".
For example, should user "joe" on system "my.host.com" be running xmcd 2.1,
a read request for his currenly playing CD might look like this:
cmd=cddb+read+rock+12345678&amp;hello=joe+my.host.com+xmcd+2.1&amp;proto=1
The server will perform the implied "proto" and "cddb hello" commands,
and then perform the requested "cddb read" command.
Server response to the command is encapsulated in the HTTP server response,
and appears in the "Entity-Body" exactly as it would appear using the CDDBP
protocol. Note that the HTTP response "Entity-Header" is not guaranteed to
contain a "Content-Length" field, so clients should be prepared to accept
variable length input. This is no different from operation under CDDBP. The
header will always contain a Mime "Content-Type" field which describes the
body of data as "text/plain".
For more detailed information on HTTP and Mime, see RFC 1945 and RFC 1521.
</pre>
</tr></td>
<tr><td align=center><font face=Arial,Helvetica>
&nbsp;
</tr></td>
</table></tr></td></table></center></td><td>&nbsp;</td>
</tr></table></td></tr></table><br><br>
<font face=Arial,Helvetica size=1><center>
<br>
<br>
<br>
<br>
</body>
</html>

159
acme/bin/source/acd/discid Normal file
View File

@ -0,0 +1,159 @@
CDDB DISCID
-----------
Both forms of CDDB access require that the software compute a "disc
ID" which is an identifier that is used to access the CDDB. The disc
ID is a 8-digit hexadecimal (base-16) number, computed using data from
a CD's Table-of-Contents (TOC) in MSF (Minute Second Frame) form. The
algorithm is listed below in Appendix A.
It is crucial that your software compute the disc ID correctly. If it
does not generate the correct disc ID, it will not be compatible with CDDB.
Moreover, if your software submits CDDB entries with bad disc IDs to the
CDDB archives, it could compromise the integrity of the CDDB.
[...]
APPENDIX A - CDDB DISCID ALGORITHM
----------------------------------
The following is a C code example that illustrates how to generate the
CDDB disc ID. [...] A text description
of the algorithm follows, which should contain the necessary information
to code the algorithm in any programming language.
struct toc {
int min;
int sec;
int frame;
};
struct toc cdtoc[100];
int
read_cdtoc_from_drive(void)
{
/* Do whatever is appropriate to read the TOC of the CD
* into the cdtoc[] structure array.
*/
return (tot_trks);
}
int
cddb_sum(int n)
{
int ret;
/* For backward compatibility this algorithm must not change */
ret = 0;
while (n > 0) {
ret = ret + (n % 10);
n = n / 10;
}
return (ret);
}
unsigned long
cddb_discid(int tot_trks)
{
int i,
t = 0,
n = 0;
/* For backward compatibility this algorithm must not change */
i = 0;
while (i < tot_trks) {
n = n + cddb_sum((cdtoc[i].min * 60) + cdtoc[i].sec);
i++;
}
t = ((cdtoc[tot_trks].min * 60) + cdtoc[tot_trks].sec) -
((cdtoc[0].min * 60) + cdtoc[0].sec);
return ((n % 0xff) << 24 | t << 8 | tot_trks);
}
main()
{
int tot_trks;
tot_trks = read_cdtoc_from_drive();
printf("The discid is %08x", cddb_discid(tot_trks));
}
This code assumes that your compiler and architecture support 32-bit
integers.
The cddb_discid function computes the discid based on the CD's TOC data
in MSF form. The frames are ignored for this purpose. The function is
passed a parameter of tot_trks (which is the total number of tracks on
the CD), and returns the discid integer number.
It is assumed that cdtoc[] is an array of data structures (records)
containing the fields min, sec and frame, which are the minute, second
and frame offsets (the starting location) of each track. This
information is read from the TOC of the CD. There are actually
tot_trks + 1 "active" elements in the array, the last one being the
offset of the lead-out (also known as track 0xAA).
The function loops through each track in the TOC, and for each track
it takes the (M * 60) + S (total offset in seconds) of the track and
feeds it to cddb_sum() function, which simply adds the value of each digit
in the decimal string representation of the number. A running sum of this
result for each track is kept in the variable n.
At the end of the loop:
1. t is calculated by subtracting the (M * 60) + S offset of the lead-out
minus the (M * 60) + S offset of first track (yielding the length of
the disc in seconds).
2. The result of (n modulo FFh) is left-shifted by 24 bits.
3. t is left shifted by 8.
The bitwise-OR operation of result 2., 3. and the tot_trks number is
used as the discid.
The discid is represented in hexadecimal form for the purpose of
xmcd cddb file names and the DISCID= field in the xmcd cddb file itself.
If the hexadecimal string is less than 8 characters long, it is
zero-padded to 8 characters (i.e., 3a8f07 becomes 003a8f07). All
alpha characters in the string should be in lower case, where
applicable.
Important note for clients using the MS-Windows MCI interface:
The Windows MCI interface does not provide the MSF location of the
lead-out. Thus, you must compute the lead-out location by taking the
starting position of the last track and add the length of the last track
to it. However, the MCI interface returns the length of the last track
as ONE FRAME SHORT of the actual length found in the CD's TOC. In most
cases this does not affect the disc ID generated, because we truncate
the frame count when computing the disc ID anyway. However, if the
lead-out track has an actual a frame count of 0, the computed quantity
(based on the MSF data returned from the MCI interface) would result in
the seconds being one short and the frame count be 74. For example,
a CD with the last track at an offset of 48m 32s 12f and having a
track length of 2m 50s 63f has a lead-out offset of 51m 23s 0f. Windows
MCI incorrectly reports the length as 2m 50s 62f, which would yield a
lead-out offset of 51m 22s 74f, which causes the resulting truncated
disc length to be off by one second. This will cause an incorrect disc
ID to be generated. You should thus add one frame to the length of the
last track when computing the location of the lead-out.
The easiest way for Windows clients to compute the lead-out given information
in MSF format is like this:
(offset_minutes * 60 * 75) + (offset_seconds * 75) + offset_frames +
(length_minutes * 60 * 75) + (length_seconds * 75) + length_frames + 1 = X
Where X is the offset of the lead-out in frames. To find the lead-out in
seconds, simply divide by 75 and discard the remainder.

View File

@ -0,0 +1,220 @@
<html><html>
<head>
<title>::freedb.org::</title>
</head>
<body bgcolor="#FFFFFF" text="#000000" link="#101070" vlink="#101070">
<center>
<table cellpadding=0 cellspacing=0 border=0 width="99%" align=center><tr><td align=left>
<a href="/">
<table border=0>
<td bgcolor="#ffffff">
<table border=0 width=100% cellpadding=0 cellspacing=0>
<td bgcolor=#101070>
<table border=0>
<td bgcolor=#ffffff><font face="Arial,Helvetica,Lucida" color="#101070" size=8><b>freedb</b></td>
<td bgcolor=#101070><font face="Arial,Helvetica,Lucida" color="#ffffff" size=8><b>.org</b></td>
</table>
</td>
<tr>
<td align=right><font face="Arial,Helvetica,Lucida" color="#101070" size=2><b>a free approach to cddbp
</table>
</td>
</table>
</a>
</td><td align=right width=100%>
<form action="search.php" method=post>
<font face=Arial,Helvetica size=2><input type=name name=query width=20 size=20 length=20>
</td>
<td align=right>&nbsp;&nbsp;<input type=image src=images/menu/english/search.gif border=0 align=middle></td>
</form>
</td></tr></table><br>
<table cellpadding=0 cellspacing=0 border=0 width="99%" bgcolor=101070><tr><td>
<table cellpadding=5 cellspacing=1 border=0 width="100%" bgcolor=FFFFFF><tr><td>
<font face=Lucida,Verdana,Arial,Helvetica size=2>freedb.org - a free approach to cddbp</td></tr></table></td></tr></table><P>
<table width="99%" align=center cellpadding=0 cellspacing=0 border=0><tr>
<td valign=top rowspan=5>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Main Menu</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
<li><a href=index.php>Home</a>
<li><a href=topics.php>News-Topics</a>
<li><a href=sections.php?op=listarticles&secid=1>About</a>
<li><a href=sections.php?op=listarticles&secid=2>Developers</a>
<li><a href=sections.php?op=listarticles&secid=3>Applications</a>
<li><a href=sections.php?op=listarticles&secid=7>Download</a>
<li><a href=forum/index.php>Forum</a>
<li><a href=http://freedb.music.sk/search/>Web-based Search</a>
<li><a href=links.php>Web Links</a>
<li><a href=user.php>Your Account</a>
<li><a href=submit.php>Submit News</a>
</font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>FAQ</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
Our FAQ can be found <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=26">here</a>.<br>
Please read the FAQ before asking questions via email. </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Contact</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
General questions:<br>
<a href="mailto:info@freedb.org">info@freedb.org</a><hr>
Databaseupdates:<br>
<a href="mailto:updates@freedb.org">updates@freedb.org</a><br>
(<b>NOT</b> for submission!)<hr>
Please keep in mind that we are NOT the Nero-Support and please do not send CD-submits to the adresses above.<br>
Submits have to go to:<br>
<a href="mailto:freedb-submit@freedb.org">freedb-submit@freedb.org</a> </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<br>
<table border=0><tr><td>
<table width="115" border="0" cellpadding="0" cellspacing="0"><tr valign="top" bgcolor="#101070">
<td bgcolor="#FFFFFF"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
<td><img src="themes/SlashOcean/cl.gif" width="7" height="10"></td>
<td><font face="verdana,helvetica,arial" size="1" color="#ffffff"><B>Downloads</B></font></td>
<td align="right"><img src="themes/SlashOcean/cr.gif" width="7" height="10" alt=""></td>
<td bgcolor="#FFFFFF" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3"></td>
</tr></table>
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
<tr bgcolor="#ffffff">
<td background="themes/SlashOcean/sl.gif"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
<td width="100%">
<table width="100%" border="0" cellpadding="5" cellspacing="0"><tr>
<td><font face="verdana,helvetica,arial" size="1">
The link to the database downloads is <a href="/sections.php?op=viewarticle&artid=12">here</a> </font></td>
</tr></table>
</td><td background="themes/SlashOcean/sr.gif" align="right"><img src="themes/SlashOcean/pix.gif" width="3" height="3" alt=""></td>
</tr>
<tr bgcolor="#101070"><td colspan="3"><img src="themes/SlashOcean/pix.gif" width="1" height="1"></td></tr>
</table>
</td>
</tr></td></table>
<td>&nbsp;</td><td valign="top" width="100%">
<!-- columna de inicio -->
<center>
<table border=0 cellpadding=1 cellspacing=0 width=100% bgcolor=000000><tr><td>
<table border=0 cellpadding=8 cellspacing=0 width=100% bgcolor=FFFFFF>
<tr><td align=left><font face=Arial,Helvetica size=3>
<b>Mailinglists</b><br>
<font size=2>
<br><br>
There are a couple of mailinglists available:
<h4>fdb-apps@freedb.org</h4>
This mailinglist is intended for all developers that want to
exchange tips and tricks, codesnippets and so on. Subcribe
to this list by sending an email to<br>
<a href="mailto:fdb-apps-request@freedb.org">fdb-apps-request@freedb.org</a><br>
with the word "subscribe" in the body of the email.
<h4>fdb-dev@freedb.org</h4>
This list is for anyone interested in developing the freedb.org
server software. Subcribe
to this list by sending an email to<br>
<a href="mailto:fdb-dev-request@freedb.org">fdb-dev-request@freedb.org</a><br>
with the word "subscribe" in the body of the email.
</tr></td>
<tr><td align=center><font face=Arial,Helvetica>
&nbsp;
</tr></td>
</table></tr></td></table></center></td><td>&nbsp;</td>
</tr></table></td></tr></table><br><br>
<font face=Arial,Helvetica size=1><center>
<br>
<br>
<br>
<br>
</body>
</html>

135
acme/bin/source/acd/main.c Normal file
View File

@ -0,0 +1,135 @@
#include "acd.h"
int debug;
void
usage(void)
{
fprint(2, "usage: acd dev\n");
threadexitsall("usage");
}
Alt
mkalt(Channel *c, void *v, int op)
{
Alt a;
memset(&a, 0, sizeof(a));
a.c = c;
a.v = v;
a.op = op;
return a;
}
void
freetoc(Toc *t)
{
int i;
free(t->title);
for(i=0; i<t->ntrack; i++)
free(t->track[i].title);
}
void
eventwatcher(Drive *d)
{
enum { STATUS, WEVENT, TOCDISP, DBREQ, DBREPLY, NALT };
Alt alts[NALT+1];
Toc nt, tdb;
Event *e;
Window *w;
Cdstatus s;
char buf[40];
w = d->w;
alts[STATUS] = mkalt(d->cstatus, &s, CHANRCV);
alts[WEVENT] = mkalt(w->cevent, &e, CHANRCV);
alts[TOCDISP] = mkalt(d->ctocdisp, &nt, CHANRCV);
alts[DBREQ] = mkalt(d->cdbreq, &tdb, CHANNOP);
alts[DBREPLY] = mkalt(d->cdbreply, &nt, CHANRCV);
alts[NALT] = mkalt(nil, nil, CHANEND);
for(;;) {
switch(alt(alts)) {
case STATUS:
//DPRINT(2, "s...");
d->status = s;
if(s.state == Scompleted) {
s.state = Sunknown;
advancetrack(d, w);
}
//DPRINT(2, "status %d %d %d %M %M\n", s.state, s.track, s.index, s.abs, s.rel);
sprint(buf, "%d:%2.2d", s.rel.m, s.rel.s);
setplaytime(w, buf);
break;
case WEVENT:
//DPRINT(2, "w...");
acmeevent(d, w, e);
break;
case TOCDISP:
//DPRINT(2,"td...");
freetoc(&d->toc);
d->toc = nt;
drawtoc(w, d, &d->toc);
tdb = nt;
alts[DBREQ].op = CHANSND;
break;
case DBREQ: /* sent */
//DPRINT(2,"dreq...");
alts[DBREQ].op = CHANNOP;
break;
case DBREPLY:
//DPRINT(2,"drep...");
freetoc(&d->toc);
d->toc = nt;
redrawtoc(w, &d->toc);
break;
}
}
}
void
threadmain(int argc, char **argv)
{
Scsi *s;
Drive *d;
char buf[80];
ARGBEGIN{
case 'v':
debug++;
scsiverbose++;
}ARGEND
if(argc != 1)
usage();
fmtinstall('M', msfconv);
if((s = openscsi(argv[0])) == nil)
error("opening scsi: %r");
d = malloc(sizeof(*d));
if(d == nil)
error("out of memory");
memset(d, 0, sizeof d);
d->scsi = s;
d->w = newwindow();
d->ctocdisp = chancreate(sizeof(Toc), 0);
d->cdbreq = chancreate(sizeof(Toc), 0);
d->cdbreply = chancreate(sizeof(Toc), 0);
d->cstatus = chancreate(sizeof(Cdstatus), 0);
proccreate(wineventproc, d->w, STACK);
proccreate(cddbproc, d, STACK);
proccreate(cdstatusproc, d, STACK);
cleanname(argv[0]);
snprint(buf, sizeof(buf), "%s/", argv[0]);
winname(d->w, buf);
wintagwrite(d->w, "Stop Pause Resume Eject Ingest ", 5+6+7+6+7);
eventwatcher(d);
}

View File

@ -0,0 +1,22 @@
</$objtype/mkfile
TARG=acd
BIN=/acme/bin/$objtype
OFILES=\
acme.$O\
cddb.$O\
main.$O\
mmc.$O\
util.$O\
win.$O\
HFILES=acd.h
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
${TARG:%=/acme/bin/386/%}\
</sys/src/cmd/mkone

303
acme/bin/source/acd/mmc.c Normal file
View File

@ -0,0 +1,303 @@
#include "acd.h"
int
msfconv(Fmt *fp)
{
Msf m;
m = va_arg(fp->args, Msf);
fmtprint(fp, "%d.%d.%d", m.m, m.s, m.f);
return 0;
}
static int
status(Drive *d)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0xBD;
return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
}
static int
playmsf(Drive *d, Msf start, Msf end)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x47;
cmd[3] = start.m;
cmd[4] = start.s;
cmd[5] = start.f;
cmd[6] = end.m;
cmd[7] = end.s;
cmd[8] = end.f;
return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
}
int
playtrack(Drive *d, int start, int end)
{
Toc *t;
t = &d->toc;
if(t->ntrack == 0)
return -1;
if(start < 0)
start = 0;
if(end >= t->ntrack)
end = t->ntrack-1;
if(end < start)
end = start;
return playmsf(d, t->track[start].start, t->track[end].end);
}
int
resume(Drive *d)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x4B;
cmd[8] = 0x01;
return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
}
int
pause(Drive *d)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x4B;
return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
}
int
stop(Drive *d)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x4E;
return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
}
int
eject(Drive *d)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x1B;
cmd[1] = 1;
cmd[4] = 2;
return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
}
int
ingest(Drive *d)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x1B;
cmd[1] = 1;
cmd[4] = 3;
return scsi(d->scsi, cmd, sizeof cmd, nil, 0, Snone);
}
static Msf
rdmsf(uchar *p)
{
Msf msf;
msf.m = p[0];
msf.s = p[1];
msf.f = p[2];
return msf;
}
static ulong
rdlba(uchar *p)
{
return (p[0]<<16) | (p[1]<<8) | p[2];
}
/* not a Drive, so that we don't accidentally touch Drive.toc */
int
gettoc(Scsi *s, Toc *t)
{
int i, n;
uchar cmd[12];
uchar resp[1024];
Again:
memset(t, 0, sizeof(*t));
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x43;
cmd[1] = 0x02;
cmd[7] = sizeof(resp)>>8;
cmd[8] = sizeof(resp);
s->changetime = 1;
/* scsi sets nchange, changetime */
if(scsi(s, cmd, sizeof cmd, resp, sizeof(resp), Sread) < 4)
return -1;
if(s->changetime == 0) {
t->ntrack = 0;
werrstr("no media");
return -1;
}
if(t->nchange == s->nchange && t->changetime != 0)
return 0;
t->nchange = s->nchange;
t->changetime = s->changetime;
if(t->ntrack > MTRACK)
t->ntrack = MTRACK;
DPRINT(2, "%d %d\n", resp[3], resp[2]);
t->ntrack = resp[3]-resp[2]+1;
t->track0 = resp[2];
n = ((resp[0]<<8) | resp[1])+2;
if(n < 4+8*(t->ntrack+1)) {
werrstr("bad read0 %d %d", n, 4+8*(t->ntrack+1));
return -1;
}
for(i=0; i<=t->ntrack; i++) /* <=: track[ntrack] = end */
t->track[i].start = rdmsf(resp+4+i*8+5);
for(i=0; i<t->ntrack; i++)
t->track[i].end = t->track[i+1].start;
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x43;
cmd[7] = sizeof(resp)>>8;
cmd[8] = sizeof(resp);
if(scsi(s, cmd, sizeof cmd, resp, sizeof(resp), Sread) < 4)
return -1;
if(s->changetime != t->changetime || s->nchange != t->nchange) {
fprint(2, "disk changed underfoot; repeating\n");
goto Again;
}
n = ((resp[0]<<8) | resp[1])+2;
if(n < 4+8*(t->ntrack+1)) {
werrstr("bad read");
return -1;
}
for(i=0; i<=t->ntrack; i++)
t->track[i].bstart = rdlba(resp+4+i*8+5);
for(i=0; i<t->ntrack; i++)
t->track[i].bend = t->track[i+1].bstart;
return 0;
}
static void
dumptoc(Toc *t)
{
int i;
fprint(1, "%d tracks\n", t->ntrack);
for(i=0; i<t->ntrack; i++)
print("%d. %M-%M (%lud-%lud)\n", i+1,
t->track[i].start, t->track[i].end,
t->track[i].bstart, t->track[i].bend);
}
static void
ping(Drive *d)
{
uchar cmd[12];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x43;
scsi(d->scsi, cmd, sizeof(cmd), nil, 0, Snone);
}
static int
playstatus(Drive *d, Cdstatus *stat)
{
uchar cmd[12], resp[16];
memset(cmd, 0, sizeof cmd);
cmd[0] = 0x42;
cmd[1] = 0x02;
cmd[2] = 0x40;
cmd[3] = 0x01;
cmd[7] = sizeof(resp)>>8;
cmd[8] = sizeof(resp);
if(scsi(d->scsi, cmd, sizeof(cmd), resp, sizeof(resp), Sread) < 0)
return -1;
switch(resp[1]){
case 0x11:
stat->state = Splaying;
break;
case 0x12:
stat->state = Spaused;
break;
case 0x13:
stat->state = Scompleted;
break;
case 0x14:
stat->state = Serror;
break;
case 0x00: /* not supported */
case 0x15: /* no current status to return */
default:
stat->state = Sunknown;
break;
}
stat->track = resp[6];
stat->index = resp[7];
stat->abs = rdmsf(resp+9);
stat->rel = rdmsf(resp+13);
return 0;
}
void
cdstatusproc(void *v)
{
Drive *d;
Toc t;
Cdstatus s;
t.changetime = ~0;
t.nchange = ~0;
threadsetname("cdstatusproc");
d = v;
DPRINT(2, "cdstatus %d\n", getpid());
for(;;) {
ping(d);
//DPRINT(2, "d %d %d t %d %d\n", d->scsi->changetime, d->scsi->nchange, t.changetime, t.nchange);
if(playstatus(d, &s) == 0)
send(d->cstatus, &s);
if(d->scsi->changetime != t.changetime || d->scsi->nchange != t.nchange) {
if(gettoc(d->scsi, &t) == 0) {
DPRINT(2, "sendtoc...\n");
if(debug) dumptoc(&t);
send(d->ctocdisp, &t);
} else
DPRINT(2, "error: %r\n");
}
sleep(1000);
}
}

View File

@ -0,0 +1,32 @@
acd is composed of four procs
wineventproc (win.c:/^wineventproc)
reads acme window events, sends them along w->cevent.
cdstatusproc (mmc.c:/^cdstatusproc)
reads cd status once per second, sending
status updates to d->cstatus.
detects disk changes, sends new tocs to d->ctocdisp.
cddbproc (cddb.c:/^cddbproc)
reads tocs from d->cdbreq, if it finds
translations in the cddb, sends new tocs to d->cdbreply.
eventwatcher (main.c:/^eventwatcher)
the main event loop.
reads status from d->cstatus.
reads events from w->cevent.
reads new tocs to display from d->ctocdisp.
sends new tocs to translate to d->cdbreq.
reads new translated tocs from d->cdbreply.
an interesting bug in the original design:
both cdstatusproc and the eventwatcher proc
issue scsi commands. (the eventwatcher responds to
things such as Play, Stop, etc., as well as advancing the track.)
the sd(3) driver did not expect overlapped commands,
and crashed.
this has been fixed by making the scsi(2) commands threadsafe,
and making the sd(3) driver more robust.

220
acme/bin/source/acd/submit Normal file
View File

@ -0,0 +1,220 @@
CDDB SUBMISSION
---------------
Your software may allow users to enter CDDB data and then submit them
to the freedb archive.
There are two methods of submission: <a href="#email">via e-mail</a> or <a href="#http">via http</a> using submit.cgi
<a name="email"></a>1. Submission via e-mail
------------------------
Your software has to send the entry to the
following address:
freedb-submit@freedb.org
You may implement a button or somesuch in your software's user-interface
to facilitate this. The destination e-mail address should be made
user-configurable.
There should be one e-mail message per freedb entry. The mail Subject
line should be in the form "cddb category discid". For example:
Subject: cddb rock 850f970b
The body of the e-mail message should be in the format of a CDDB file
entry as described <a href="http://freedb.freedb.org/software/old/DBFORMAT">here</a>. The messages should contain only
plain ASCII text. Do not attach encoded information or add special
escape sequences.
Note that the disc ID specified in the mail Subject line should
also appear in the list of disc IDs in the DISCID= field of the
CDDB file entry. If not, it is considered an error and the submission
will be rejected.
You should only allow categories that are currently supported by the
freedb (blues, classical, country, data, folk, jazz, misc, newage,
reggae, rock, soundtrack). Submissions specifying unsupported
categories will be rejected.
Please do not allow a user to submit CD database entries that
have completely unfilled contents (i.e., blank information in the
disc artist/title as well as the track titles, or filled with
useless default information like "track 1", "track 2", etc.).
While the current CD database server checks and rejects submissions
that have a blank DTITLE line, it doesn't (and can't feasibly) check
the track titles effectively, nor can it check any of these fields
if they are filled with a default string. If it were, it would
have to be hacked to know about the default strings of every possible
client.
Thus, please design your client with this in mind. This is a somewhat
tricky thing to do, as some CDs contain blank tracks with no titles
and you need to allow for that. An example minimum requirement
that a CD player client should meet is listed below:
1. Don't allow the "send" or "submit" feature to be activated if
the CD database information form is not edited at all.
2. Check that the disc artist/title contains something (that the user
typed in).
3. Check that all of the tracks have a title filled in by the user
(some (but not all!) may be blank, but not the default string).
This should minimize the number of useless garbage being submitted
into the CD database.
Before you release your software, please be sure that it produces
submissions that adheres to the CDDB file format, and that the frame
offset, disc length, and disc ID information are correctly computed.
For testing, please make your software send submissions to the
following e-mail address (rather than the real submission site at
freedb-submit@freedb.org):
test-submit@freedb.org
The test address performs sanity checking on the CDDB submission and
sends back pass/fail confirmation, but does not actually deposit the
entry in the CD database.
<a name="http"></a>2. Submission via http
----------------------
For submit via http, your application has to transmit the entry to the
database through a CGI program at the following URL:
http://freedb.freedb.org/~cddb/submit.cgi
Submissions are made through the CGI program as follows. You must only use
the "POST" method of sending data; "GET" is not supported. There are several
HTTP "Entity-Header" fields that must be included in the data followed by a
blank line, followed by the "Entity-Body" (a.k.a the CDDB entry) in the
format described in Appendix B below. The required header fields are:
Category: CDDB_category
Discid: CDDB_discid
User-Email: user@domain
Submit-Mode: test_or_submit
Content-Length: length_of_CDDB_entry
Where:
- "CDDB_category" is one of the valid CDDB categories (blues, classical,
country, data, folk, jazz, misc, newage, reggae, rock, soundtrack).
Invalid categories will result in the entry being rejected.
- "CDDB_discid" is the 8-digit hex CDDB disc ID of the entry as described in
the "<a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=6">Discid howto</a>" section. This must be the same disc ID that appears
in the "DISCID=" section of the entry being submitted. If not, the entry
will be rejected.
- "user@domain" is the valid email address of the user submitting the entry.
This is required in case a submission failure notice must be sent to the
user.
- "test_or_submit" is the word "test" or "submit" (without the surrounding
quotes) to indicate whether the submission is a test submission or a real
submission to the database, respectively. See <a href="#testsubmission">below</a> for an explanation of
test submissions.
- "length_of_CDDB_entry" is the size in bytes of the CDDB entry being
submitted. This number does not include the length of the header or the
blank line separating the HTTP header and the CDDB entry.
There are several additional optional HTTP header fields that may also
be specified (but which are currently not used by the freedb):
Charset: character_set_of_CDDB_entry
X-Cddbd-Note: message for user
Where:
- "character_set_of_CDDB_entry" is one of ISO-8859-1 or US-ASCII (lower case
may be used if desired). This specifies to the CDDB server which character
set the CDDB entry has been encoded in. If your application knows the
user's character set, then you should specify it here. Only these two
character sets are supported currently. DO NOT specify the character set
if your application does not have any way of verifying the user's character
set (i.e. do not guess; it's better not to specify it at all).
- "message for user" is an arbitrary message to be included at the top of
any rejection notice that may be sent to the submitting user.
An example submission showing the HTTP command, "Entity-Header" and "Entity-
Body" follows:
POST /~cddb/submit.cgi HTTP/1.0
Category: rock
Discid: 2a09310a
User-Email: joe@joeshost.joesdomain.com
Submit-Mode: submit
Charset: ISO-8859-1
X-Cddbd-Note: Problems with Super CD Player? Send email to support@supercd.com.
Content-Length: 820
# xmcd
#
# Track frame offsets:
[ data omitted in this example for brevity ]
PLAYORDER=
Note the blank line between the "Content-Length" header field and the
"# xmcd" which marks the beginning of the CDDB entry.
When your application submits an entry through the CGI program, it will
respond with a 3-digit response code indicating whether or not the entry has
been forwarded to the freedb server for inclusion in the database, followed
by a textual description of the response code. For example:
200 OK, submission has been sent.
400 Internal error: failed to forward submission.
500 Missing required header information.
These are but a few of the possible responses.
See the description of the <a href="http://freedb.freedb.org/sections.php?op=viewarticle&artid=28">CDDB server protocol</a> for more information on
handling response codes.
The body of the freedb entry being submitted should be sent verbatim as
described in the <a href="http://freedb.freedb.org/software/old/DBFORMAT">database-format specification</a>. DO NOT encode the data in any
way before transmitting it; data must be sent as raw text. For example,
Windows programmers should not use the Windows URL encode function prior to
calling the submit CGI program. Doing so may lead to corrupt data being sent
and also possibly to rejected submissions.
You may implement a button or somesuch in your software's user interface
to initiate submissions. Rejected submissions are automatically returned
via email to the sender specified in the "User-Email" header field with an
explanation of the reason for the rejection.
Please do not allow a user to submit CD database entries that
have completely unfilled contents (i.e., blank information in the
disc artist/title as well as the track titles, or filled with
useless default information like "track 1", "track 2", etc.).
While the current CD database server checks and rejects submissions
that have a blank DTITLE line, it doesn't (and can't feasibly) check
the track titles effectively, nor can it check any of these fields
if they are filled with a default string. If it were, it would
have to be hacked to know about the default strings of every possible
client.
Thus, please design your client with this in mind. This is a somewhat
tricky thing to do, as some CDs contain blank tracks with no titles
and you need to allow for that. An example minimum requirement
that a CD player client should meet is listed below:
1. Don't allow the "send" or "submit" feature to be activated if
the CD database information form is not edited at all.
2. Check that the disc artist/title contains something (that the user
typed in).
3. Check that all of the tracks have a title filled in by the user.
(some (but not all!) may be blank, but not the default string).
Before you release your software, please be sure that it produces
submissions that adhere to the CDDB file format, and that the frame
offset, disc length, and disc ID information are correctly computed.
For testing, please make your software send submissions with the
"Submit-Mode" HTTP header field set to "test".
<a name="testsubmission"></a>CDDB submissions sent in test mode will be sanity-checked by the freedb server
and pass/fail confirmation sent back to the submitter, but will not actually
be deposited in the CD database. Please DO NOT send submisions in "submit"
mode until you have tested your program with several different CD's.

59
acme/bin/source/acd/toc.c Normal file
View File

@ -0,0 +1,59 @@
#include "acd.h"
Toc thetoc;
void
tocthread(void *v)
{
Drive *d;
threadsetname("tocthread");
d = v;
DPRINT(2, "recv ctocdisp?...");
while(recv(d->ctocdisp, &thetoc) == 1) {
DPRINT(2, "recv ctocdisp!...");
drawtoc(d->w, &thetoc);
DPRINT(2, "send dbreq...\n");
send(d->ctocdbreq, &thetoc);
}
}
void
freetoc(Toc *t)
{
int i;
free(t->title);
for(i=0; i<t->ntrack; i++)
free(t->track[i].title);
}
void
cddbthread(void *v)
{
Drive *d;
Toc t;
threadsetname("cddbthread");
d = v;
while(recv(d->ctocdbreply, &t) == 1) {
if(thetoc.nchange == t.nchange) {
freetoc(&thetoc);
thetoc = t;
redrawtoc(d->w, &thetoc);
}
}
}
void
cdstatusthread(void *v)
{
Drive *d;
Cdstatus s;
d = v;
for(;;)
recv(d->cstat, &s);
}

View File

@ -0,0 +1,89 @@
#include "acd.h"
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
return p;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
strcpy(u, s);
strcat(u, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
strcpy(u, s);
strcat(u, sep);
strcat(u, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
return s;
}
void
error(char *fmt, ...)
{
int n;
va_list arg;
char buf[256];
fprint(2, "Mail: ");
va_start(arg, fmt);
n = vsnprint(buf, sizeof buf, fmt, arg);
va_end(arg);
write(2, buf, n);
write(2, "\n", 1);
threadexitsall(fmt);
}
void
ctlprint(int fd, char *fmt, ...)
{
int n;
va_list arg;
char buf[256];
va_start(arg, fmt);
n = vsnprint(buf, sizeof buf, fmt, arg);
va_end(arg);
if(write(fd, buf, n) != n)
error("control file write error: %r");
}

320
acme/bin/source/acd/win.c Normal file
View File

@ -0,0 +1,320 @@
#include "acd.h"
Window*
newwindow(void)
{
char buf[12];
Window *w;
w = emalloc(sizeof(Window));
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
ctlprint(w->ctl, "noscroll\n");
w->id = atoi(buf);
w->event = winopenfile(w, "event");
w->addr = -1; /* will be opened when needed */
w->body = nil;
w->data = -1;
w->cevent = chancreate(sizeof(Event*), 0);
if(w->cevent == nil)
error("cevent is nil: %r");
return w;
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
threadsetname("wineventproc");
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
}
int
winopenfile(Window *w, char *f)
{
char buf[64];
int fd;
sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
fd = open(buf, ORDWR|OCEXEC);
if(fd < 0)
error("can't open window file %s: %r", f);
return fd;
}
void
wintagwrite(Window *w, char *s, int n)
{
int fd;
fd = winopenfile(w, "tag");
if(write(fd, s, n) != n)
error("tag write: %r");
close(fd);
}
void
winname(Window *w, char *s)
{
ctlprint(w->ctl, "name %s\n", s);
}
void
winopenbody(Window *w, int mode)
{
char buf[256];
sprint(buf, "/mnt/wsys/%d/body", w->id);
w->body = Bopen(buf, mode|OCEXEC);
if(w->body == nil)
error("can't open window body file: %r");
}
void
winclosebody(Window *w)
{
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
}
void
winwritebody(Window *w, char *s, int n)
{
if(w->body == nil)
winopenbody(w, OWRITE);
if(Bwrite(w->body, s, n) != n)
error("write error to window: %r");
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
{
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
}
void
winwriteevent(Window *w, Event *e)
{
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
static int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
void
winread(Window *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(w->data < 0)
w->data = winopenfile(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
error("error writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n <= 0)
error("reading data: %r");
nr = nrunes(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
windormant(Window *w)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
int
windel(Window *w, int sure)
{
if(sure)
write(w->ctl, "delete\n", 7);
else if(write(w->ctl, "del\n", 4) != 4)
return 0;
/* event proc will die due to read error from event file */
windormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return 1;
}
void
winclean(Window *w)
{
if(w->body)
Bflush(w->body);
ctlprint(w->ctl, "clean\n");
}
int
winsetaddr(Window *w, char *addr, int errok)
{
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
}
return 0;
}
char*
winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
{
char *s;
int m, na, n;
if(w->body != nil)
winclosebody(w);
winopenbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = realloc(s, na+1);
}
m = Bread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
winclosebody(w);
*np = n;
return s;
}

View File

@ -0,0 +1,584 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "win.h"
#include "adict.h"
char *prog = "adict";
char *lprog = "/bin/adict";
char *xprog = "/bin/dict";
char *dict, *pattern, *curaddr[MAXMATCH], *curone, *args[6], buffer[80];
char abuffer[80], fbuffer[80], pbuffer[80];
int curindex, count, Eopen, Mopen;
Win Mwin, Ewin, Dwin;
void openwin(char*, char*, Win*, int);
void handle(Win*, int);
void rexec(void*);
void pexec(void*);
int getaddr(char*);
void
usage(void)
{
threadprint(2, "usage: %s [-d dictname] [pattern]\n", argv0);
threadexitsall(nil);
}
void
threadmain(int argc, char** argv)
{
ARGBEGIN{
case 'd':
dict = strdup(ARGF());
break;
default:
usage();
}ARGEND
/* if running as other name, note that fact */
if(access(argv0, AEXIST) == 0)
lprog = argv0;
switch(argc){
case 1:
pattern = pbuffer;
strcpy(pattern,argv[0]);
if(dict == nil)
dict = "oed";
break;
case 0:
break;
default:
usage();
}
if ((dict == nil) && (pattern == nil))
openwin(prog,"", &Dwin, Dictwin);
if (pattern == nil)
openwin(prog,"",&Ewin, Entrywin);
if ((count = getaddr(pattern)) <= 1)
openwin(prog,"Prev Next", &Ewin, Entrywin);
else
openwin(prog, "", &Mwin, Matchwin);
}
static int
procrexec(char *xprog, ...)
{
int fpipe[2];
void *rexarg[4];
Channel *c;
va_list va;
int i;
char *p;
pipe(fpipe);
va_start(va, xprog);
p = xprog;
for(i=0; p && i+1<nelem(args); i++){
args[i] = p;
p = va_arg(va, char*);
}
args[i] = nil;
c = chancreate(sizeof(ulong), 0);
rexarg[0] = xprog;
rexarg[1] = args;
rexarg[2] = fpipe;
rexarg[3] = c;
proccreate(rexec, rexarg, 8192);
recvul(c);
chanfree(c);
close(fpipe[1]);
return fpipe[0];
}
int
getaddr(char *pattern)
{
/* Get char offset into dictionary of matches. */
int fd, i;
Biobuf inbuf;
char *bufptr;
char *obuf;
if (pattern == nil) {
curone = nil;
curindex = 0;
curaddr[curindex] = nil;
return 0;
}
sprint(buffer,"/%s/A", pattern);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
Binit(&inbuf, fd, OREAD);
i = 0;
curindex = 0;
while ((bufptr = Brdline(&inbuf, '\n')) != nil && (i < (MAXMATCH-1))) {
bufptr[Blinelen(&inbuf)-1] = 0;
obuf=bufptr;
while (bufptr[0] != '#' && bufptr[0] != 0) bufptr++;
if(bufptr[0] == 0)
print("whoops buf «%s»\n", obuf);
curaddr[i] = malloc(strlen(bufptr));
strcpy(curaddr[i], bufptr);
i++;
}
curaddr[i] = nil;
if (i == MAXMATCH)
threadprint(2, "Too many matches!\n");
Bterm(&inbuf);
close(fd);
curone = curaddr[curindex];
return(i);
}
char*
getpattern(char *addr)
{
/* Get the pattern corresponding to an absolute address.*/
int fd;
char *res, *t;
res = nil;
sprint(buffer,"%sh", addr);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
if (read(fd, pbuffer, 80) > 80)
threadprint(2, "Error in getting addres from dict.\n");
else {
t = pbuffer;
/* remove trailing whitespace, newline */
if (t != nil){
while(*t != 0 && *t != '\n')
t++;
if(t == 0 && t > pbuffer)
t--;
while(t >= pbuffer && (*t==' ' || *t=='\n' || *t=='\t' || *t=='\r'))
*t-- = 0;
}
res = pbuffer;
}
close(fd);
return(res);
}
char*
chgaddr(int dir)
{
/* Increment or decrement the current address (curone). */
int fd;
char *res, *t;
res = nil;
if (dir < 0)
sprint(buffer,"%s-a", curone);
else
sprint(buffer,"%s+a", curone);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
if (read(fd, abuffer, 80) > 80)
threadprint(2, "Error in getting addres from dict.\n");
else {
res = abuffer;
while (*res != '#') res++;
t = res;
while ((*t != '\n') && (t != nil)) t++;
if (t != nil) *t = 0;
}
close(fd);
return(res);
}
void
dispdicts(Win *cwin)
{
/* Display available dictionaries in window. */
int fd, nb, i;
char buf[1024], *t;
fd = procrexec(xprog, "-d", "?", nil);
wreplace(cwin, "0,$","",0); /* Clear window */
while ((nb = read(fd, buf, 1024)) > 0) {
t = buf;
i = 0;
if (strncmp("Usage", buf, 5) == 0) { /* Remove first line. */
while (t[0] != '\n') {
t++;
i++;
}
t++;
i++;
}
wwritebody(cwin, t, nb-i);
}
close(fd);
wclean(cwin);
}
void
dispentry(Win *cwin)
{
/* Display the current selection in window. */
int fd, nb;
char buf[BUFSIZE];
if (curone == nil) {
if (pattern != nil) {
sprint(buf,"Pattern not found.\n");
wwritebody(cwin, buf, 19);
wclean(cwin);
}
return;
}
sprint(buffer,"%sp", curone);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
wreplace(cwin, "0,$","",0); /* Clear window */
while ((nb = read(fd, buf, BUFSIZE)) > 0) {
wwritebody(cwin, buf, nb);
}
close(fd);
wclean(cwin);
}
void
dispmatches(Win *cwin)
{
/* Display the current matches. */
int fd, nb;
char buf[BUFSIZE];
sprint(buffer,"/%s/H", pattern);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
while ((nb = read(fd, buf, BUFSIZE)) > 0)
wwritebody(cwin, buf, nb);
close(fd);
wclean(cwin);
}
char*
format(char *s)
{
/* Format a string to be written in window tag. Acme doesn't like */
/* non alpha-num's in the tag line. */
char *t, *h;
t = fbuffer;
if (s == nil) {
*t = 0;
return t;
}
strcpy(t, s);
h = t;
while (*t != 0) {
if (!(((*t >= 'a') && (*t <= 'z')) ||
((*t >= 'A') && (*t <= 'Z')) ||
((*t >= '0') && (*t <= '9'))))
*t = '_';
t++;
}
if (strlen(h) > MAXTAG)
h[MAXTAG] = 0;
if (strcmp(s,h) == 0) return s;
return h;
}
void
openwin(char *name, char *buttons, Win *twin, int wintype)
{
char buf[80];
wnew(twin);
if (wintype == Dictwin)
sprint(buf,"%s",name);
else
if ((wintype == Entrywin) && (count > 1))
sprint(buf,"%s/%s/%s/%d",name, dict, format(pattern), curindex+1);
else
sprint(buf,"%s/%s/%s",name, dict, format(pattern));
wname(twin, buf);
wtagwrite(twin, buttons, strlen(buttons));
wclean(twin);
wdormant(twin);
if (wintype == Dictwin)
dispdicts(twin);
if (wintype == Matchwin) {
Mopen = True;
dispmatches(twin);
}
if (wintype == Entrywin) {
Eopen = True;
dispentry(twin);
}
handle(twin, wintype);
}
void
vopenwin(void *v)
{
void **arg;
char *name, *buttons;
Win *twin;
int wintype;
arg = v;
name = arg[0];
buttons = arg[1];
twin = arg[2];
wintype = (int)arg[3];
sendul(arg[4], 0);
openwin(name, buttons, twin, wintype);
threadexits(nil);
}
void
procopenwin(char *name, char *buttons, Win *twin, int wintype)
{
void *arg[5];
Channel *c;
c = chancreate(sizeof(ulong), 0);
arg[0] = name;
arg[1] = buttons;
arg[2] = twin;
arg[3] = (void*)wintype;
arg[4] = c;
proccreate(vopenwin, arg, 8192);
recvul(c);
chanfree(c);
}
void
rexec(void *v)
{
void **arg;
char *prog;
char **args;
int *fd;
Channel *c;
arg = v;
prog = arg[0];
args = arg[1];
fd = arg[2];
c = arg[3];
rfork(RFENVG|RFFDG);
dup(fd[1], 1);
close(fd[1]);
close(fd[0]);
procexec(c, prog, args);
threadprint(2, "Remote pipe execution failed: %s %r\n", prog);
abort();
threadexits(nil);
}
void
pexec(void *v)
{
void **arg;
char *prog;
char **args;
Channel *c;
arg = v;
prog = arg[0];
args = arg[1];
c = arg[2];
procexec(c, prog, args);
threadprint(2, "Remote execution failed: %s %r\n", prog);
abort();
threadexits(nil);
}
void
procpexec(char *prog, char **args)
{
void *rexarg[4];
Channel *c;
c = chancreate(sizeof(ulong), 0);
rexarg[0] = prog;
rexarg[1] = args;
rexarg[2] = c;
proccreate(pexec, rexarg, 8192);
recvul(c);
chanfree(c);
}
void
kill(void)
{
/* Kill all processes related to this one. */
int fd;
sprint(buffer, "/proc/%d/notepg", getpid());
fd = open(buffer, OWRITE);
rfork(RFNOTEG);
write(fd, "kill", 4);
}
int
command(char *com, Win *w, int wintype)
{
char *buf;
if (strncmp(com, "Del", 3) == 0) {
switch(wintype){
case Entrywin:
if (wdel(w)) {
Eopen = False;
threadexits(nil);
}
break;
case Dictwin:
if (wdel(w))
threadexits(nil);
break;
case Matchwin:
kill();
if (Eopen)
if (~wdel(&Ewin)) /* Remove the entry window */
wdel(&Ewin);
if (!wdel(w))
wdel(w);
threadexits(nil);
break;
}
return True;
}
if (strncmp(com, "Next", 4) == 0){
if (curone != nil) {
curone = chgaddr(1);
buf = getpattern(curone);
sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
wname(w, buffer);
dispentry(w);
}
return True;
}
if (strncmp(com, "Prev",4) == 0){
if (curone != nil) {
curone = chgaddr(-1);
buf = getpattern(curone);
sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
wname(w, buffer);
dispentry(w);
}
return True;
}
if (strncmp(com, "Nmatch",6) == 0){
if (curaddr[++curindex] == nil)
curindex = 0;
curone = curaddr[curindex];
if (curone != nil) {
sprint(buffer,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
wname(w, buffer);
dispentry(w);
}
return True;
}
return False;
}
void
handle(Win *w, int wintype)
{
Event e, e2, ea, etoss;
char *s, *t, buf[80];
int tmp, na;
while (True) {
wevent(w, &e);
switch(e.c2){
default:
/* threadprint(2,"unknown message %c%c\n", e.c1, e.c2); */
break;
case 'i':
/* threadprint(2,"'%s' inserted in tag at %d\n", e.b, e.q0);*/
break;
case 'I':
/* threadprint(2,"'%s' inserted in body at %d\n", e.b, e.q0);*/
break;
case 'd':
/* threadprint(2, "'%s' deleted in tag at %d\n", e.b, e.q0);*/
break;
case 'D':
/* threadprint(2, "'%s' deleted in body at %d\n", e.b, e.q0);*/
break;
case 'x':
case 'X': /* Execute command. */
if (e.flag & 2)
wevent(w, &e2);
if(e.flag & 8){
wevent(w, &ea);
wevent(w, &etoss);
na = ea.nb;
} else
na = 0;
s = e.b;
if ((e.flag & 2) && e.nb == 0)
s = e2.b;
if(na){
t = malloc(strlen(s)+1+na+1);
snprint(t, strlen(s)+1+na+1, "%s %s", s, ea.b);
s = t;
}
/* if it's a long message, it can't be for us anyway */
if(!command(s, w, wintype)) /* send it back */
wwriteevent(w, &e);
if(na)
free(s);
break;
case 'l':
case 'L': /* Look for something. */
if (e.flag & 2)
wevent(w, &e);
wclean(w); /* Set clean bit. */
if (wintype == Dictwin) {
strcpy(buf, e.b);
args[0] = lprog;
args[1] = "-d";
args[2] = buf;
args[3] = nil;
procpexec(lprog, args); /* New adict with chosen dict. */
}
if (wintype == Entrywin) {
strcpy(buf, e.b);
args[0] = lprog;
args[1] = "-d";
args[2] = dict;
args[3] = buf;
args[4] = nil;
procpexec(lprog, args); /* New adict with chosen pattern. */
}
if (wintype == Matchwin) {
tmp = atoi(e.b) - 1;
if ((tmp >= 0) && (tmp < MAXMATCH) && (curaddr[tmp] != nil)) {
curindex = tmp;
curone = curaddr[curindex];
/* Display selected match. */
if (Eopen) {
sprint(buf,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
wname(&Ewin, buf);
dispentry(&Ewin);
}
else
procopenwin(prog,"Nmatch Prev Next", &Ewin, Entrywin);
}
}
break;
}
}
}

View File

@ -0,0 +1,315 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "win.h"
void*
erealloc(void *p, uint n)
{
p = realloc(p, n);
if(p == nil)
threadprint(2, "realloc failed: %r");
return p;
}
void
wnew(Win *w)
{
char buf[12];
w->ctl = open("/mnt/acme/new/ctl", ORDWR);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
threadprint (2, "can't open window ctl file: %r");
ctlwrite(w, "noscroll\n");
w->winid = atoi(buf);
w->event = openfile(w, "event");
w->addr = -1; /* will be opened when needed */
w->body = nil;
w->data = -1;
}
int
openfile(Win *w, char *f)
{
char buf[64];
int fd;
sprint(buf, "/mnt/acme/%d/%s", w->winid, f);
fd = open(buf, ORDWR|OCEXEC);
if(fd < 0)
threadprint (2,"can't open window %s file: %r", f);
return fd;
}
void
openbody(Win *w, int mode)
{
char buf[64];
sprint(buf, "/mnt/acme/%d/body", w->winid);
w->body = Bopen(buf, mode|OCEXEC);
if(w->body == nil)
threadprint(2,"can't open window body file: %r");
}
void
wwritebody(Win *w, char *s, int n)
{
if(w->body == nil)
openbody(w, OWRITE);
if(Bwrite(w->body, s, n) != n)
threadprint(2,"write error to window: %r");
Bflush(w->body);
}
void
wreplace(Win *w, char *addr, char *repl, int nrepl)
{
if(w->addr < 0)
w->addr = openfile(w, "addr");
if(w->data < 0)
w->data = openfile(w, "data");
if(write(w->addr, addr, strlen(addr)) < 0){
threadprint(2, "mail: warning: badd address %s:%r\n", addr);
return;
}
if(write(w->data, repl, nrepl) != nrepl)
threadprint(2, "writing data: %r");
}
static int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
void
wread(Win *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr < 0)
w->addr = openfile(w, "addr");
if(w->data < 0)
w->data = openfile(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
threadprint(2,"writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n <= 0)
threadprint(2,"reading data: %r");
nr = nrunes(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
wselect(Win *w, char *addr)
{
if(w->addr < 0)
w->addr = openfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0)
threadprint(2,"writing addr");
ctlwrite(w, "dot=addr\n");
}
void
wtagwrite(Win *w, char *s, int n)
{
int fd;
fd = openfile(w, "tag");
if(write(fd, s, n) != n)
threadprint(2,"tag write: %r");
close(fd);
}
void
ctlwrite(Win *w, char *s)
{
int n;
n = strlen(s);
if(write(w->ctl, s, n) != n)
threadprint(2,"write error to ctl file: %r");
}
int
wdel(Win *w)
{
if(write(w->ctl, "del\n", 4) != 4)
return False;
wdormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return True;
}
void
wname(Win *w, char *s)
{
char buf[128];
sprint(buf, "name %s\n", s);
ctlwrite(w, buf);
}
void
wclean(Win *w)
{
if(w->body)
Bflush(w->body);
ctlwrite(w, "clean\n");
}
void
wdormant(Win *w)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
int
getec(Win *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0)
threadprint(2,"event read error: %r");
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
geten(Win *w)
{
int n, c;
n = 0;
while('0'<=(c=getec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
threadprint(2, "event number syntax");
return n;
}
int
geter(Win *w, char *buf, int *nb)
{
Rune r;
int n;
r = getec(w);
buf[0] = r;
n = 1;
if(r < Runeself)
goto Return;
while(!fullrune(buf, n))
buf[n++] = getec(w);
chartorune(&r, buf);
Return:
*nb = n;
return r;
}
void
wevent(Win *w, Event *e)
{
int i, nb;
e->c1 = getec(w);
e->c2 = getec(w);
e->q0 = geten(w);
e->q1 = geten(w);
e->flag = geten(w);
e->nr = geten(w);
if(e->nr > EVENTSIZE)
threadprint(2, "wevent: event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = geter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(getec(w) != '\n')
threadprint(2, "wevent: event syntax 2");
}
void
wslave(Win *w, Channel *ce)
{
Event e;
while(recv(ce, &e) >= 0)
wevent(w, &e);
}
void
wwriteevent(Win *w, Event *e)
{
threadprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
int
wreadall(Win *w, char **sp)
{
char *s;
int m, na, n;
if(w->body != nil)
Bterm(w->body);
openbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = erealloc(s, na+1);
}
m = Bread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
Bterm(w->body);
w->body = nil;
*sp = s;
return n;
}

View File

@ -0,0 +1,591 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "win.h"
#include "adict.h"
enum
{
STACK = 8192,
};
char *prog = "adict";
char *lprog = "/bin/adict";
char *xprog = "/bin/dict";
char *dict, *pattern, *curaddr[MAXMATCH], *curone, *args[6], buffer[80];
char abuffer[80], fbuffer[80], pbuffer[80];
int curindex, count, Eopen, Mopen;
Win Mwin, Ewin, Dwin;
void openwin(char*, char*, Win*, int);
void handle(Win*, int);
void rexec(void*);
void pexec(void*);
int getaddr(char*);
void
usage(void)
{
fprint(2, "usage: %s [-d dictname] [pattern]\n", argv0);
threadexitsall(nil);
}
int mainstacksize = STACK;
void
threadmain(int argc, char** argv)
{
ARGBEGIN{
case 'd':
dict = strdup(ARGF());
break;
default:
usage();
}ARGEND
/* if running as other name, note that fact */
if(access(argv0, AEXIST) == 0)
lprog = argv0;
switch(argc){
case 1:
pattern = pbuffer;
strcpy(pattern,argv[0]);
if(dict == nil)
dict = "pgw";
break;
case 0:
break;
default:
usage();
}
if ((dict == nil) && (pattern == nil))
openwin(prog,"", &Dwin, Dictwin);
if (pattern == nil)
openwin(prog,"",&Ewin, Entrywin);
if ((count = getaddr(pattern)) <= 1)
openwin(prog,"Prev Next", &Ewin, Entrywin);
else
openwin(prog, "", &Mwin, Matchwin);
}
static int
procrexec(char *xprog, ...)
{
int fpipe[2];
void *rexarg[4];
Channel *c;
va_list va;
int i;
char *p;
pipe(fpipe);
va_start(va, xprog);
p = xprog;
for(i=0; p && i+1<nelem(args); i++){
args[i] = p;
p = va_arg(va, char*);
}
args[i] = nil;
c = chancreate(sizeof(ulong), 0);
rexarg[0] = xprog;
rexarg[1] = args;
rexarg[2] = fpipe;
rexarg[3] = c;
proccreate(rexec, rexarg, STACK);
recvul(c);
chanfree(c);
close(fpipe[1]);
return fpipe[0];
}
int
getaddr(char *pattern)
{
/* Get char offset into dictionary of matches. */
int fd, i;
Biobuf inbuf;
char *bufptr;
char *obuf;
if (pattern == nil) {
curone = nil;
curindex = 0;
curaddr[curindex] = nil;
return 0;
}
sprint(buffer,"/%s/A", pattern);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
Binit(&inbuf, fd, OREAD);
i = 0;
curindex = 0;
while ((bufptr = Brdline(&inbuf, '\n')) != nil && (i < (MAXMATCH-1))) {
bufptr[Blinelen(&inbuf)-1] = 0;
obuf=bufptr;
while (bufptr[0] != '#' && bufptr[0] != 0) bufptr++;
if(bufptr[0] == 0)
print("whoops buf «%s»\n", obuf);
curaddr[i] = malloc(strlen(bufptr));
strcpy(curaddr[i], bufptr);
i++;
}
curaddr[i] = nil;
if (i == MAXMATCH)
fprint(2, "Too many matches!\n");
Bterm(&inbuf);
close(fd);
curone = curaddr[curindex];
return(i);
}
char*
getpattern(char *addr)
{
/* Get the pattern corresponding to an absolute address.*/
int fd;
char *res, *t;
res = nil;
sprint(buffer,"%sh", addr);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
if (read(fd, pbuffer, 80) > 80)
fprint(2, "Error in getting addres from dict.\n");
else {
t = pbuffer;
/* remove trailing whitespace, newline */
if (t != nil){
while(*t != 0 && *t != '\n')
t++;
if(t == 0 && t > pbuffer)
t--;
while(t >= pbuffer && (*t==' ' || *t=='\n' || *t=='\t' || *t=='\r'))
*t-- = 0;
}
res = pbuffer;
}
close(fd);
return(res);
}
char*
chgaddr(int dir)
{
/* Increment or decrement the current address (curone). */
int fd;
char *res, *t;
res = nil;
if (dir < 0)
sprint(buffer,"%s-a", curone);
else
sprint(buffer,"%s+a", curone);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
if (read(fd, abuffer, 80) > 80)
fprint(2, "Error in getting addres from dict.\n");
else {
res = abuffer;
while (*res != '#') res++;
t = res;
while ((*t != '\n') && (t != nil)) t++;
if (t != nil) *t = 0;
}
close(fd);
return(res);
}
void
dispdicts(Win *cwin)
{
/* Display available dictionaries in window. */
int fd, nb, i;
char buf[1024], *t;
fd = procrexec(xprog, "-d", "?", nil);
wreplace(cwin, "0,$","",0); /* Clear window */
while ((nb = read(fd, buf, 1024)) > 0) {
t = buf;
i = 0;
if (strncmp("Usage", buf, 5) == 0) { /* Remove first line. */
while (t[0] != '\n') {
t++;
i++;
}
t++;
i++;
}
wwritebody(cwin, t, nb-i);
}
close(fd);
wclean(cwin);
}
void
dispentry(Win *cwin)
{
/* Display the current selection in window. */
int fd, nb;
char buf[BUFSIZE];
if (curone == nil) {
if (pattern != nil) {
sprint(buf,"Pattern not found.\n");
wwritebody(cwin, buf, 19);
wclean(cwin);
}
return;
}
sprint(buffer,"%sp", curone);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
wreplace(cwin, "0,$","",0); /* Clear window */
while ((nb = read(fd, buf, BUFSIZE)) > 0) {
wwritebody(cwin, buf, nb);
}
close(fd);
wclean(cwin);
}
void
dispmatches(Win *cwin)
{
/* Display the current matches. */
int fd, nb;
char buf[BUFSIZE];
sprint(buffer,"/%s/H", pattern);
fd = procrexec(xprog, "-d", dict, "-c", buffer, nil);
while ((nb = read(fd, buf, BUFSIZE)) > 0)
wwritebody(cwin, buf, nb);
close(fd);
wclean(cwin);
}
char*
format(char *s)
{
/* Format a string to be written in window tag. Acme doesn't like */
/* non alpha-num's in the tag line. */
char *t, *h;
t = fbuffer;
if (s == nil) {
*t = 0;
return t;
}
strcpy(t, s);
h = t;
while (*t != 0) {
if (!(((*t >= 'a') && (*t <= 'z')) ||
((*t >= 'A') && (*t <= 'Z')) ||
((*t >= '0') && (*t <= '9'))))
*t = '_';
t++;
}
if (strlen(h) > MAXTAG)
h[MAXTAG] = 0;
if (strcmp(s,h) == 0) return s;
return h;
}
void
openwin(char *name, char *buttons, Win *twin, int wintype)
{
char buf[80];
wnew(twin);
if (wintype == Dictwin)
sprint(buf,"%s",name);
else
if ((wintype == Entrywin) && (count > 1))
sprint(buf,"%s/%s/%s/%d",name, dict, format(pattern), curindex+1);
else
sprint(buf,"%s/%s/%s",name, dict, format(pattern));
wname(twin, buf);
wtagwrite(twin, buttons, strlen(buttons));
wclean(twin);
wdormant(twin);
if (wintype == Dictwin)
dispdicts(twin);
if (wintype == Matchwin) {
Mopen = True;
dispmatches(twin);
}
if (wintype == Entrywin) {
Eopen = True;
dispentry(twin);
}
handle(twin, wintype);
}
void
vopenwin(void *v)
{
void **arg;
char *name, *buttons;
Win *twin;
int wintype;
arg = v;
name = arg[0];
buttons = arg[1];
twin = arg[2];
wintype = (int)arg[3];
sendul(arg[4], 0);
openwin(name, buttons, twin, wintype);
threadexits(nil);
}
void
procopenwin(char *name, char *buttons, Win *twin, int wintype)
{
void *arg[5];
Channel *c;
c = chancreate(sizeof(ulong), 0);
arg[0] = name;
arg[1] = buttons;
arg[2] = twin;
arg[3] = (void*)wintype;
arg[4] = c;
proccreate(vopenwin, arg, STACK);
recvul(c);
chanfree(c);
}
void
rexec(void *v)
{
void **arg;
char *prog;
char **args;
int *fd;
Channel *c;
arg = v;
prog = arg[0];
args = arg[1];
fd = arg[2];
c = arg[3];
rfork(RFENVG|RFFDG);
dup(fd[1], 1);
close(fd[1]);
close(fd[0]);
procexec(c, prog, args);
fprint(2, "Remote pipe execution failed: %s %r\n", prog);
abort();
threadexits(nil);
}
void
pexec(void *v)
{
void **arg;
char *prog;
char **args;
Channel *c;
arg = v;
prog = arg[0];
args = arg[1];
c = arg[2];
procexec(c, prog, args);
fprint(2, "Remote execution failed: %s %r\n", prog);
abort();
threadexits(nil);
}
void
procpexec(char *prog, char **args)
{
void *rexarg[4];
Channel *c;
c = chancreate(sizeof(ulong), 0);
rexarg[0] = prog;
rexarg[1] = args;
rexarg[2] = c;
proccreate(pexec, rexarg, STACK);
recvul(c);
chanfree(c);
}
void
kill(void)
{
/* Kill all processes related to this one. */
int fd;
sprint(buffer, "/proc/%d/notepg", getpid());
fd = open(buffer, OWRITE);
rfork(RFNOTEG);
write(fd, "kill", 4);
}
int
command(char *com, Win *w, int wintype)
{
char *buf;
if (strncmp(com, "Del", 3) == 0) {
switch(wintype){
case Entrywin:
if (wdel(w)) {
Eopen = False;
threadexits(nil);
}
break;
case Dictwin:
if (wdel(w))
threadexits(nil);
break;
case Matchwin:
kill();
if (Eopen)
if (~wdel(&Ewin)) /* Remove the entry window */
wdel(&Ewin);
if (!wdel(w))
wdel(w);
threadexits(nil);
break;
}
return True;
}
if (strncmp(com, "Next", 4) == 0){
if (curone != nil) {
curone = chgaddr(1);
buf = getpattern(curone);
sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
wname(w, buffer);
dispentry(w);
}
return True;
}
if (strncmp(com, "Prev",4) == 0){
if (curone != nil) {
curone = chgaddr(-1);
buf = getpattern(curone);
sprint(buffer,"%s/%s/%s", prog, dict, format(buf));
wname(w, buffer);
dispentry(w);
}
return True;
}
if (strncmp(com, "Nmatch",6) == 0){
if (curaddr[++curindex] == nil)
curindex = 0;
curone = curaddr[curindex];
if (curone != nil) {
sprint(buffer,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
wname(w, buffer);
dispentry(w);
}
return True;
}
return False;
}
void
handle(Win *w, int wintype)
{
Event e, e2, ea, etoss;
char *s, *t, buf[80];
int tmp, na;
while (True) {
wevent(w, &e);
switch(e.c2){
default:
/* fprint(2,"unknown message %c%c\n", e.c1, e.c2); */
break;
case 'i':
/* fprint(2,"'%s' inserted in tag at %d\n", e.b, e.q0);*/
break;
case 'I':
/* fprint(2,"'%s' inserted in body at %d\n", e.b, e.q0);*/
break;
case 'd':
/* fprint(2, "'%s' deleted in tag at %d\n", e.b, e.q0);*/
break;
case 'D':
/* fprint(2, "'%s' deleted in body at %d\n", e.b, e.q0);*/
break;
case 'x':
case 'X': /* Execute command. */
if (e.flag & 2)
wevent(w, &e2);
if(e.flag & 8){
wevent(w, &ea);
wevent(w, &etoss);
na = ea.nb;
} else
na = 0;
s = e.b;
if ((e.flag & 2) && e.nb == 0)
s = e2.b;
if(na){
t = malloc(strlen(s)+1+na+1);
snprint(t, strlen(s)+1+na+1, "%s %s", s, ea.b);
s = t;
}
/* if it's a long message, it can't be for us anyway */
if(!command(s, w, wintype)) /* send it back */
wwriteevent(w, &e);
if(na)
free(s);
break;
case 'l':
case 'L': /* Look for something. */
if (e.flag & 2)
wevent(w, &e);
wclean(w); /* Set clean bit. */
if (wintype == Dictwin) {
strcpy(buf, e.b);
args[0] = lprog;
args[1] = "-d";
args[2] = buf;
args[3] = nil;
procpexec(lprog, args); /* New adict with chosen dict. */
}
if (wintype == Entrywin) {
strcpy(buf, e.b);
args[0] = lprog;
args[1] = "-d";
args[2] = dict;
args[3] = buf;
args[4] = nil;
procpexec(lprog, args); /* New adict with chosen pattern. */
}
if (wintype == Matchwin) {
tmp = atoi(e.b) - 1;
if ((tmp >= 0) && (tmp < MAXMATCH) && (curaddr[tmp] != nil)) {
curindex = tmp;
curone = curaddr[curindex];
/* Display selected match. */
if (Eopen) {
sprint(buf,"%s/%s/%s/%d",prog,dict,format(pattern),curindex+1);
wname(&Ewin, buf);
dispentry(&Ewin);
}
else
procopenwin(prog,"Nmatch Prev Next", &Ewin, Entrywin);
}
}
break;
}
}
}

View File

@ -0,0 +1,10 @@
enum
{
Matchwin,
Entrywin,
Dictwin
};
#define MAXTAG 20
#define MAXMATCH 100
#define BUFSIZE 4096

26
acme/bin/source/adict/man Normal file
View File

@ -0,0 +1,26 @@
adict [-d dictionary] [pattern]
adict with no arguments opens a window that displays all the currently
available dictionaries. To select a dictionary, click the right mouse button on
its name.
-d dictionary Opens a window that interfaces to the specified dictionary. To
look up a word, enter it in the window, and click the right mouse button on it.
[pattern] If no dictionary is specified, adict looks up the pattern in "oed" (Oxford
English Dictionary). If more than one entry is found, adict opens a window
displaying the headers of the matching entries. To display a particular entry
click the right mouse button on the number to its left.
Quit Exit and remove all windows associated with this one.
Nmatch Display the next matching entry.
Next Display the next entry in the dictionary.
Prev Display the previous entry in the dictionary.
Nmatch works independently of Prev and Next.
Any word in the window displaying an entry can be looked up in the selected
dictionary by clicking the right mouse button on that word.

View File

@ -0,0 +1,11 @@
</$objtype/mkfile
TARG=adict
HFILES=win.h
OFILES=adict.$O\
win.$O\
BIN= /acme/bin/$objtype
</sys/src/cmd/mkone

315
acme/bin/source/adict/win.c Normal file
View File

@ -0,0 +1,315 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "win.h"
void*
erealloc(void *p, uint n)
{
p = realloc(p, n);
if(p == nil)
fprint(2, "realloc failed: %r");
return p;
}
void
wnew(Win *w)
{
char buf[12];
w->ctl = open("/mnt/acme/new/ctl", ORDWR);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
fprint (2, "can't open window ctl file: %r");
ctlwrite(w, "noscroll\n");
w->winid = atoi(buf);
w->event = openfile(w, "event");
w->addr = -1; /* will be opened when needed */
w->body = nil;
w->data = -1;
}
int
openfile(Win *w, char *f)
{
char buf[64];
int fd;
sprint(buf, "/mnt/acme/%d/%s", w->winid, f);
fd = open(buf, ORDWR|OCEXEC);
if(fd < 0)
fprint (2,"can't open window %s file: %r", f);
return fd;
}
void
openbody(Win *w, int mode)
{
char buf[64];
sprint(buf, "/mnt/acme/%d/body", w->winid);
w->body = Bopen(buf, mode|OCEXEC);
if(w->body == nil)
fprint(2,"can't open window body file: %r");
}
void
wwritebody(Win *w, char *s, int n)
{
if(w->body == nil)
openbody(w, OWRITE);
if(Bwrite(w->body, s, n) != n)
fprint(2,"write error to window: %r");
Bflush(w->body);
}
void
wreplace(Win *w, char *addr, char *repl, int nrepl)
{
if(w->addr < 0)
w->addr = openfile(w, "addr");
if(w->data < 0)
w->data = openfile(w, "data");
if(write(w->addr, addr, strlen(addr)) < 0){
fprint(2, "mail: warning: badd address %s:%r\n", addr);
return;
}
if(write(w->data, repl, nrepl) != nrepl)
fprint(2, "writing data: %r");
}
static int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
void
wread(Win *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr < 0)
w->addr = openfile(w, "addr");
if(w->data < 0)
w->data = openfile(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
fprint(2,"writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n <= 0)
fprint(2,"reading data: %r");
nr = nrunes(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
wselect(Win *w, char *addr)
{
if(w->addr < 0)
w->addr = openfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0)
fprint(2,"writing addr");
ctlwrite(w, "dot=addr\n");
}
void
wtagwrite(Win *w, char *s, int n)
{
int fd;
fd = openfile(w, "tag");
if(write(fd, s, n) != n)
fprint(2,"tag write: %r");
close(fd);
}
void
ctlwrite(Win *w, char *s)
{
int n;
n = strlen(s);
if(write(w->ctl, s, n) != n)
fprint(2,"write error to ctl file: %r");
}
int
wdel(Win *w)
{
if(write(w->ctl, "del\n", 4) != 4)
return False;
wdormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return True;
}
void
wname(Win *w, char *s)
{
char buf[128];
sprint(buf, "name %s\n", s);
ctlwrite(w, buf);
}
void
wclean(Win *w)
{
if(w->body)
Bflush(w->body);
ctlwrite(w, "clean\n");
}
void
wdormant(Win *w)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
int
getec(Win *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0)
fprint(2,"event read error: %r");
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
geten(Win *w)
{
int n, c;
n = 0;
while('0'<=(c=getec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
fprint(2, "event number syntax");
return n;
}
int
geter(Win *w, char *buf, int *nb)
{
Rune r;
int n;
r = getec(w);
buf[0] = r;
n = 1;
if(r < Runeself)
goto Return;
while(!fullrune(buf, n))
buf[n++] = getec(w);
chartorune(&r, buf);
Return:
*nb = n;
return r;
}
void
wevent(Win *w, Event *e)
{
int i, nb;
e->c1 = getec(w);
e->c2 = getec(w);
e->q0 = geten(w);
e->q1 = geten(w);
e->flag = geten(w);
e->nr = geten(w);
if(e->nr > EVENTSIZE)
fprint(2, "wevent: event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = geter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(getec(w) != '\n')
fprint(2, "wevent: event syntax 2");
}
void
wslave(Win *w, Channel *ce)
{
Event e;
while(recv(ce, &e) >= 0)
wevent(w, &e);
}
void
wwriteevent(Win *w, Event *e)
{
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
int
wreadall(Win *w, char **sp)
{
char *s;
int m, na, n;
if(w->body != nil)
Bterm(w->body);
openbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = erealloc(s, na+1);
}
m = Bread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
Bterm(w->body);
w->body = nil;
*sp = s;
return n;
}

View File

@ -0,0 +1,59 @@
enum
{
False,
True,
EVENTSIZE=256,
};
typedef struct Event Event;
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
typedef struct Win Win;
struct Win
{
int winid;
int addr;
Biobuf *body;
int ctl;
int data;
int event;
char buf[512];
char *bufp;
int nbuf;
};
int dead(Win*);
void wnew(Win*);
void wwritebody(Win*, char *s, int n);
void wread(Win*, uint, uint, char*);
void wclean(Win*);
void wname(Win*, char*);
void wdormant(Win*);
void wevent(Win*, Event*);
void wtagwrite(Win*, char*, int);
void wwriteevent(Win*, Event*);
void wslave(Win*, Channel*); /* chan(Event) */
void wreplace(Win*, char*, char*, int);
void wselect(Win*, char*);
int wdel(Win*);
int wreadall(Win*, char**);
void ctlwrite(Win*, char*);
int getec(Win*);
int geten(Win*);
int geter(Win*, char*, int*);
int openfile(Win*, char*);
void openbody(Win*, int);

27
acme/bin/source/mkfile Normal file
View File

@ -0,0 +1,27 @@
</$objtype/mkfile
TARG=\
mkwnew\
spout\
OFILES=
HFILES=
LIB=
DIRS=win
BIN=../$objtype
</sys/src/cmd/mkmany
all:V: all.dirs
install:V: install.dirs
clean:V: clean.dirs
nuke:V: nuke.dirs
%.dirs:VQ:
for (i in $DIRS) @{
echo mk $i
cd $i
mk $stem
}

45
acme/bin/source/mkwnew.c Normal file
View File

@ -0,0 +1,45 @@
#include <u.h>
#include <libc.h>
void
main(int argc, char *argv[])
{
int i, fd, pid, n;
char wdir[256];
int dflag;
dflag = 0;
ARGBEGIN{
case 'd':
dflag = 1;
break;
default:
fprint(2, "usage: wnew [-d] [label]\n");
}ARGEND
pid = getpid();
wdir[0] = '\0';
if(!dflag)
getwd(wdir, sizeof wdir);
if(argc>0)
for(i=0; i<argc; i++)
snprint(wdir, sizeof wdir, "%s%c%s", wdir, i==0? '/' : '-', argv[i]);
else
snprint(wdir, sizeof wdir, "%s/-win", wdir);
if((fd = open("/dev/wnew", ORDWR)) < 0)
sysfatal("wnew: can't open /dev/wnew: %r");
if(fprint(fd, "%d %s", pid, wdir+dflag) < 0)
sysfatal("wnew: can't create window: %r");
if(seek(fd, 0, 0) != 0)
sysfatal("wnew: can't seek: %r");
if((n=read(fd, wdir, sizeof wdir-1)) < 0)
sysfatal("wnew: can't read window id: %r");
wdir[n] = '\0';
print("%s\n", wdir);
exits(nil);
}

123
acme/bin/source/spout.c Normal file
View File

@ -0,0 +1,123 @@
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
void spout(int, char*);
Biobuf bout;
void
main(int argc, char *argv[])
{
int i, fd;
Binit(&bout, 1, OWRITE);
if(argc == 1)
spout(0, "");
else
for(i=1; i<argc; i++){
fd = open(argv[i], OREAD);
if(fd < 0){
fprint(2, "spell: can't open %s: %r\n", argv[i]);
continue;
}
spout(fd, argv[i]);
close(fd);
}
exits(nil);
}
Biobuf b;
void
spout(int fd, char *name)
{
char *s, *t, *w;
Rune r;
int inword, wordchar;
int n, wn, wid, c, m;
char buf[1024];
Binit(&b, fd, OREAD);
n = 0;
wn = 0;
while((s = Brdline(&b, '\n')) != nil){
if(s[0] == '.')
for(c=0; c<3 && *s>' '; c++){
n++;
s++;
}
inword = 0;
w = s;
t = s;
do{
c = *(uchar*)t;
if(c < Runeself)
wid = 1;
else{
wid = chartorune(&r, t);
c = r;
}
wordchar = 0;
if(isalpha(c))
wordchar = 1;
if(inword && !wordchar){
if(c=='\'' && isalpha(t[1]))
goto Continue;
m = t-w;
if(m > 1){
memmove(buf, w, m);
buf[m] = 0;
Bprint(&bout, "%s:#%d,#%d:%s\n", name, wn, n, buf);
}
inword = 0;
}else if(!inword && wordchar){
wn = n;
w = t;
inword = 1;
}
if(c=='\\' && (isalpha(t[1]) || t[1]=='(')){
switch(t[1]){
case '(':
m = 4;
break;
case 'f':
if(t[2] == '(')
m = 5;
else
m = 3;
break;
case 's':
if(t[2] == '+' || t[2]=='-'){
if(t[3] == '(')
m = 6;
else
m = 4;
}else{
if(t[2] == '(')
m = 5;
else if(t[2]=='1' || t[2]=='2' || t[2]=='3')
m = 4;
else
m = 3;
}
break;
default:
m = 2;
}
while(m-- > 0){
if(*t == '\n')
break;
n++;
t++;
}
continue;
}
Continue:
n++;
t += wid;
}while(c != '\n');
}
Bterm(&b);
}

146
acme/bin/source/win/_fs.c Normal file
View File

@ -0,0 +1,146 @@
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "dat.h"
Channel *fschan;
Channel *writechan;
static File *devcons, *devnew;
static void
fsread(Req *r)
{
Fsevent e;
if(r->fid->file == devnew){
if(r->fid->aux==nil){
respond(r, "phase error");
return;
}
readstr(r, r->fid->aux);
respond(r, nil);
return;
}
assert(r->fid->file == devcons);
e.type = 'r';
e.r = r;
send(fschan, &e);
}
static void
fsflush(Req *r)
{
Fsevent e;
e.type = 'f';
e.r = r;
send(fschan, &e);
}
static void
fswrite(Req *r)
{
static Event *e[4];
Event *ep;
int i, j, nb, wid, pid;
Rune rune;
char *s;
char tmp[UTFmax], *t;
static int n, partial;
if(r->fid->file == devnew){
if(r->fid->aux){
respond(r, "already created a window");
return;
}
s = emalloc(r->ifcall.count+1);
memmove(s, r->ifcall.data, r->ifcall.count);
s[r->ifcall.count] = 0;
pid = strtol(s, &t, 0);
if(*t==' ')
t++;
i = newpipewin(pid, t);
free(s);
s = emalloc(32);
sprint(s, "%lud", (ulong)i);
r->fid->aux = s;
r->ofcall.count = r->ifcall.count;
respond(r, nil);
return;
}
assert(r->fid->file == devcons);
if(e[0] == nil){
for(i=0; i<nelem(e); i++){
e[i] = emalloc(sizeof(Event));
e[i]->c1 = 'S';
}
}
ep = e[n];
n = (n+1)%nelem(e);
assert(r->ifcall.count <= 8192); /* is this guaranteed by lib9p? */
nb = r->ifcall.count;
memmove(ep->b+partial, r->ifcall.data, nb);
nb += partial;
ep->b[nb] = '\0';
if(strlen(ep->b) < nb){ /* nulls in data */
t = ep->b;
for(i=j=0; i<nb; i++)
if(ep->b[i] != '\0')
t[j++] = ep->b[i];
nb = j;
t[j] = '\0';
}
/* process bytes into runes, transferring terminal partial runes into next buffer */
for(i=j=0; i<nb && fullrune(ep->b+i, nb-i); i+=wid,j++)
wid = chartorune(&rune, ep->b+i);
memmove(tmp, ep->b+i, nb-i);
partial = nb-i;
ep->nb = i;
ep->nr = j;
ep->b[i] = '\0';
if(i != 0){
sendp(win->cevent, ep);
recvp(writechan);
}
partial = nb-i;
memmove(e[n]->b, tmp, partial);
r->ofcall.count = r->ifcall.count;
respond(r, nil);
}
void
fsdestroyfid(Fid *fid)
{
if(fid->aux)
free(fid->aux);
}
Srv fs = {
.read= fsread,
.write= fswrite,
.flush= fsflush,
.destroyfid= fsdestroyfid,
.leavefdsopen= 1,
};
void
mountcons(void)
{
fschan = chancreate(sizeof(Fsevent), 0);
writechan = chancreate(sizeof(void*), 0);
fs.tree = alloctree("win", "win", DMDIR|0555, nil);
devcons = createfile(fs.tree->root, "cons", "win", 0666, nil);
if(devcons == nil)
sysfatal("creating /dev/cons: %r");
devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil);
if(devnew == nil)
sysfatal("creating /dev/wnew: %r");
threadpostmountsrv(&fs, nil, "/dev", MBEFORE);
}

651
acme/bin/source/win/_main.c Normal file
View File

@ -0,0 +1,651 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <ctype.h>
#include "dat.h"
void mainctl(void*);
void startcmd(char *[], int*);
void stdout2body(void*);
int debug;
int notepg;
int eraseinput;
int dirty = 0;
Window *win; /* the main window */
void
usage(void)
{
fprint(2, "usage: win [command]\n");
threadexitsall("usage");
}
void
threadmain(int argc, char *argv[])
{
int i, j;
char *dir, *tag, *name;
char buf[1024], **av;
quotefmtinstall();
rfork(RFNAMEG);
ARGBEGIN{
case 'd':
debug = 1;
chatty9p++;
break;
case 'e':
eraseinput = 1;
break;
case 'D':
{extern int _threaddebuglevel;
_threaddebuglevel = 1<<20;
}
}ARGEND
if(argc == 0){
av = emalloc(3*sizeof(char*));
av[0] = "rc";
av[1] = "-i";
name = getenv("sysname");
}else{
av = argv;
name = utfrrune(av[0], '/');
if(name)
name++;
else
name = av[0];
}
if(getwd(buf, sizeof buf) == 0)
dir = "/";
else
dir = buf;
dir = estrdup(dir);
tag = estrdup(dir);
tag = eappend(estrdup(tag), "/-", name);
win = newwindow();
snprint(buf, sizeof buf, "%d", win->id);
putenv("winid", buf);
winname(win, tag);
wintagwrite(win, "Send Noscroll", 5+8);
threadcreate(mainctl, win, STACK);
mountcons();
threadcreate(fsloop, nil, STACK);
startpipe();
startcmd(av, &notepg);
strcpy(buf, "win");
j = 3;
for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
strcpy(buf+j, " ");
strcpy(buf+j+1, argv[i]);
j += 1+strlen(argv[i]);
}
ctlprint(win->ctl, "scroll");
winsetdump(win, dir, buf);
}
int
EQUAL(char *s, char *t)
{
while(tolower(*s) == tolower(*t++))
if(*s++ == '\0')
return 1;
return 0;
}
int
command(Window *w, char *s)
{
while(*s==' ' || *s=='\t' || *s=='\n')
s++;
if(strcmp(s, "Delete")==0){
windel(w, 1);
threadexitsall(nil);
return 1;
}
if(strcmp(s, "Del")==0){
if(windel(w, 0))
threadexitsall(nil);
return 1;
}
if(EQUAL(s, "scroll")){
ctlprint(w->ctl, "scroll\nshow");
return 1;
}
if(EQUAL(s, "noscroll")){
ctlprint(w->ctl, "noscroll");
return 1;
}
return 0;
}
static long
utfncpy(char *to, char *from, int n)
{
char *end, *e;
e = to+n;
if(to >= e)
return 0;
end = memccpy(to, from, '\0', e - to);
if(end == nil){
end = e;
if(end[-1]&0x80){
if(end-2>=to && (end[-2]&0xE0)==0xC0)
return end-to;
if(end-3>=to && (end[-3]&0xF0)==0xE0)
return end-to;
while(end>to && (*--end&0xC0)==0x80)
;
}
}else
end--;
return end - to;
}
/* sendinput and fsloop run in the same proc (can't interrupt each other). */
static Req *q;
static Req **eq;
static int
__sendinput(Window *w, ulong q0, ulong q1)
{
char *s, *t;
int n, nb, eofchar;
static int partial;
static char tmp[UTFmax];
Req *r;
Rune rune;
if(!q)
return 0;
r = q;
n = 0;
if(partial){
Partial:
nb = partial;
if(nb > r->ifcall.count)
nb = r->ifcall.count;
memmove(r->ofcall.data, tmp, nb);
if(nb!=partial)
memmove(tmp, tmp+nb, partial-nb);
partial -= nb;
q = r->aux;
if(q == nil)
eq = &q;
r->aux = nil;
r->ofcall.count = nb;
if(debug)
fprint(2, "satisfy read with partial\n");
respond(r, nil);
return n;
}
if(q0==q1)
return 0;
s = emalloc((q1-q0)*UTFmax+1);
n = winread(w, q0, q1, s);
s[n] = '\0';
t = strpbrk(s, "\n\004");
if(t == nil){
free(s);
return 0;
}
r = q;
eofchar = 0;
if(*t == '\004'){
eofchar = 1;
*t = '\0';
}else
*++t = '\0';
nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
if(nb==0 && s<t && r->ifcall.count > 0){
partial = utfncpy(tmp, s, UTFmax);
assert(partial > 0);
chartorune(&rune, tmp);
partial = runelen(rune);
free(s);
n = 1;
goto Partial;
}
n = utfnlen(r->ofcall.data, nb);
if(nb==strlen(s) && eofchar)
n++;
r->ofcall.count = nb;
q = r->aux;
if(q == nil)
eq = &q;
r->aux = nil;
if(debug)
fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
respond(r, nil);
return n;
}
static int
_sendinput(Window *w, ulong q0, ulong *q1)
{
char buf[32];
int n;
n = __sendinput(w, q0, *q1);
if(!n || !eraseinput)
return n;
/* erase q0 to q0+n */
sprint(buf, "#%lud,#%lud", q0, q0+n);
winsetaddr(w, buf, 0);
write(w->data, buf, 0);
*q1 -= n;
return 0;
}
int
sendinput(Window *w, ulong q0, ulong *q1)
{
ulong n;
Req *oq;
n = 0;
do {
oq = q;
n += _sendinput(w, q0+n, q1);
} while(q != oq);
return n;
}
Event esendinput;
void
fsloop(void*)
{
Fsevent e;
Req **l, *r;
eq = &q;
memset(&esendinput, 0, sizeof esendinput);
esendinput.c1 = 'C';
for(;;){
while(recv(fschan, &e) == -1)
;
r = e.r;
switch(e.type){
case 'r':
*eq = r;
r->aux = nil;
eq = &r->aux;
/* call sendinput with hostpt and endpt */
sendp(win->cevent, &esendinput);
break;
case 'f':
for(l=&q; *l; l=&(*l)->aux){
if(*l == r->oldreq){
*l = (*l)->aux;
if(*l == nil)
eq = l;
respond(r->oldreq, "interrupted");
break;
}
}
respond(r, nil);
break;
}
}
}
void
sendit(char *s)
{
// char tmp[32];
write(win->body, s, strlen(s));
/*
* RSC: The problem here is that other procs can call sendit,
* so we lose our single-threadedness if we call sendinput.
* In fact, we don't even have the right queue memory,
* I think that we'll get a write event from the body write above,
* and we can do the sendinput then, from our single thread.
*
* I still need to figure out how to test this assertion for
* programs that use /srv/win*
*
winselect(win, "$", 0);
seek(win->addr, 0UL, 0);
if(read(win->addr, tmp, 2*12) == 2*12)
hostpt += sendinput(win, hostpt, atol(tmp), );
*/
}
void
execevent(Window *w, Event *e, int (*command)(Window*, char*))
{
Event *ea, *e2;
int n, na, len, needfree;
char *s, *t;
ea = nil;
e2 = nil;
if(e->flag & 2)
e2 = recvp(w->cevent);
if(e->flag & 8){
ea = recvp(w->cevent);
na = ea->nb;
recvp(w->cevent);
}else
na = 0;
needfree = 0;
s = e->b;
if(e->nb==0 && (e->flag&2)){
s = e2->b;
e->q0 = e2->q0;
e->q1 = e2->q1;
e->nb = e2->nb;
}
if(e->nb==0 && e->q0<e->q1){
/* fetch data from window */
s = emalloc((e->q1-e->q0)*UTFmax+2);
n = winread(w, e->q0, e->q1, s);
s[n] = '\0';
needfree = 1;
}else
if(na){
t = emalloc(strlen(s)+1+na+2);
sprint(t, "%s %s", s, ea->b);
if(needfree)
free(s);
s = t;
needfree = 1;
}
/* if it's a known command, do it */
/* if it's a long message, it can't be for us anyway */
if(!command(w, s) && s[0]!='\0'){ /* send it as typed text */
/* if it's a built-in from the tag, send it back */
if(e->flag & 1)
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
else{ /* send text to main window */
len = strlen(s);
if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
if(!needfree){
/* if(needfree), we left room for a newline before */
t = emalloc(len+2);
strcpy(t, s);
s = t;
needfree = 1;
}
s[len++] = '\n';
s[len] = '\0';
}
sendit(s);
}
}
if(needfree)
free(s);
}
int
hasboundary(Rune *r, int nr)
{
int i;
for(i=0; i<nr; i++)
if(r[i]=='\n' || r[i]=='\004')
return 1;
return 0;
}
void
mainctl(void *v)
{
Window *w;
Event *e;
int delta, pendingS, pendingK;
ulong hostpt, endpt;
char tmp[32];
w = v;
proccreate(wineventproc, w, STACK);
hostpt = 0;
endpt = 0;
winsetaddr(w, "0", 0);
pendingS = 0;
pendingK = 0;
for(;;){
if(debug)
fprint(2, "input range %lud-%lud\n", hostpt, endpt);
e = recvp(w->cevent);
if(debug)
fprint(2, "msg: %C %C %d %d %d %d %q\n",
e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
switch(e->c1){
default:
Unknown:
fprint(2, "unknown message %c%c\n", e->c1, e->c2);
break;
case 'C': /* input needed for /dev/cons */
if(pendingS)
pendingK = 1;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
case 'S': /* output to stdout */
sprint(tmp, "#%lud", hostpt);
winsetaddr(w, tmp, 0);
write(w->data, e->b, e->nb);
pendingS += utfnlen(e->b, e->nb);
break;
case 'E': /* write to tag or body; body happens due to sendit */
delta = e->q1-e->q0;
if(e->c2=='I'){
endpt += delta;
if(e->q0 < hostpt)
hostpt += delta;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
}
if(!islower(e->c2))
fprint(2, "win msg: %C %C %d %d %d %d %q\n",
e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
break;
case 'F': /* generated by our actions (specifically case 'S' above) */
delta = e->q1-e->q0;
if(e->c2=='D'){
/* we know about the delete by _sendinput */
break;
}
if(e->c2=='I'){
pendingS -= e->q1 - e->q0;
if(pendingS < 0)
fprint(2, "win: pendingS = %d\n", pendingS);
if(e->q0 != hostpt)
fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
endpt += delta;
hostpt += delta;
sendp(writechan, nil);
if(pendingS == 0 && pendingK){
pendingK = 0;
hostpt += sendinput(w, hostpt, &endpt);
}
break;
}
if(!islower(e->c2))
fprint(2, "win msg: %C %C %d %d %d %d %q\n",
e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
break;
case 'K':
delta = e->q1-e->q0;
switch(e->c2){
case 'D':
endpt -= delta;
if(e->q1 < hostpt)
hostpt -= delta;
else if(e->q0 < hostpt)
hostpt = e->q0;
break;
case 'I':
delta = e->q1 - e->q0;
endpt += delta;
if(endpt < e->q1) /* just in case */
endpt = e->q1;
if(e->q0 < hostpt)
hostpt += delta;
if(e->nr>0 && e->r[e->nr-1]==0x7F){
write(notepg, "interrupt", 9);
hostpt = endpt;
break;
}
if(e->q0 >= hostpt
&& hasboundary(e->r, e->nr)){
/*
* If we are between the S message (which
* we processed by inserting text in the
* window) and the F message notifying us
* that the text has been inserted, then our
* impression of the hostpt and acme's
* may be different. This could be seen if you
* hit enter a bunch of times in a con
* session. To work around the unreliability,
* only send input if we don't have an S pending.
* The same race occurs between when a character
* is typed and when we get notice of it, but
* since characters tend to be typed at the end
* of the buffer, we don't run into it. There's
* no workaround possible for this typing race,
* since we can't tell when the user has typed
* something but we just haven't been notified.
*/
if(pendingS)
pendingK = 1;
else
hostpt += sendinput(w, hostpt, &endpt);
}
break;
}
break;
case 'M': /* mouse */
delta = e->q1-e->q0;
switch(e->c2){
case 'x':
case 'X':
execevent(w, e, command);
break;
case 'l': /* reflect all searches back to acme */
case 'L':
if(e->flag & 2)
recvp(w->cevent);
winwriteevent(w, e);
break;
case 'I':
endpt += delta;
if(e->q0 < hostpt)
hostpt += delta;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
case 'D':
endpt -= delta;
if(e->q1 < hostpt)
hostpt -= delta;
else if(e->q0 < hostpt)
hostpt = e->q0;
break;
case 'd': /* modify away; we don't care */
case 'i':
break;
default:
goto Unknown;
}
}
}
}
enum
{
NARGS = 100,
NARGCHAR = 8*1024,
EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
};
struct Exec
{
char **argv;
Channel *cpid;
};
int
lookinbin(char *s)
{
if(s[0] == '/')
return 0;
if(s[0]=='.' && s[1]=='/')
return 0;
if(s[0]=='.' && s[1]=='.' && s[2]=='/')
return 0;
return 1;
}
/* adapted from mail. not entirely free of details from that environment */
void
execproc(void *v)
{
struct Exec *e;
char *cmd, **av;
Channel *cpid;
e = v;
rfork(RFCFDG|RFNOTEG);
av = e->argv;
close(0);
open("/dev/cons", OREAD);
close(1);
open("/dev/cons", OWRITE);
dup(1, 2);
cpid = e->cpid;
free(e);
procexec(cpid, av[0], av);
if(lookinbin(av[0])){
cmd = estrstrdup("/bin/", av[0]);
procexec(cpid, cmd, av);
}
error("can't exec %s: %r", av[0]);
}
void
startcmd(char *argv[], int *notepg)
{
struct Exec *e;
Channel *cpid;
char buf[64];
int pid;
e = emalloc(sizeof(struct Exec));
e->argv = argv;
cpid = chancreate(sizeof(ulong), 0);
e->cpid = cpid;
sprint(buf, "/mnt/wsys/%d", win->id);
bind(buf, "/dev/acme", MREPL);
proccreate(execproc, e, EXECSTACK);
do
pid = recvul(cpid);
while(pid == -1);
sprint(buf, "/proc/%d/notepg", pid);
*notepg = open(buf, OWRITE);
}

95
acme/bin/source/win/dat.h Normal file
View File

@ -0,0 +1,95 @@
typedef struct Fsevent Fsevent;
typedef struct Event Event;
typedef struct Message Message;
typedef struct Window Window;
enum
{
STACK = 8192,
NPIPEDATA = 8000,
NPIPE = NPIPEDATA+32,
/* EVENTSIZE is really 256 in acme, but we use events internally and want bigger buffers */
EVENTSIZE = 8192,
NEVENT = 5,
};
struct Fsevent
{
int type;
void *r;
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* file descriptors */
int ctl;
int event;
int addr;
int data;
int body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int id;
int open;
Channel *cevent;
};
extern Window* newwindow(void);
extern int winopenfile(Window*, char*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern int winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winclean(Window*);
extern int winselect(Window*, char*, int);
extern int winsetaddr(Window*, char*, int);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern void ctlprint(int, char*, ...);
extern void* emalloc(uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern void startpipe(void);
extern void sendit(char*);
extern void execevent(Window *w, Event *e, int (*)(Window*, char*));
extern void mountcons(void);
extern void fsloop(void*);
extern int newpipewin(int, char*);
extern void startpipe(void);
extern int pipecommand(Window*, char*);
extern void pipectl(void*);
#pragma varargck argpos error 1
#pragma varargck argpos ctlprint 2
extern Window *win;
extern Channel *fschan, *writechan;

147
acme/bin/source/win/fs.c Normal file
View File

@ -0,0 +1,147 @@
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "dat.h"
Channel *fschan;
Channel *writechan;
static File *devcons, *devnew;
static void
fsread(Req *r)
{
Fsevent e;
if(r->fid->file == devnew){
if(r->fid->aux==nil){
respond(r, "phase error");
return;
}
readstr(r, r->fid->aux);
respond(r, nil);
return;
}
assert(r->fid->file == devcons);
e.type = 'r';
e.r = r;
send(fschan, &e);
}
static void
fsflush(Req *r)
{
Fsevent e;
e.type = 'f';
e.r = r;
send(fschan, &e);
}
static void
fswrite(Req *r)
{
static Event *e[4];
Event *ep;
int i, j, ei, nb, wid, pid;
Rune rune;
char *s;
char tmp[UTFmax], *t;
static int n, partial;
if(r->fid->file == devnew){
if(r->fid->aux){
respond(r, "already created a window");
return;
}
s = emalloc(r->ifcall.count+1);
memmove(s, r->ifcall.data, r->ifcall.count);
s[r->ifcall.count] = 0;
pid = strtol(s, &t, 0);
if(*t==' ')
t++;
i = newpipewin(pid, t);
free(s);
s = emalloc(32);
sprint(s, "%lud", (ulong)i);
r->fid->aux = s;
r->ofcall.count = r->ifcall.count;
respond(r, nil);
return;
}
assert(r->fid->file == devcons);
if(e[0] == nil){
for(i=0; i<nelem(e); i++){
e[i] = emalloc(sizeof(Event));
e[i]->c1 = 'S';
}
}
ep = e[n];
n = (n+1)%nelem(e);
assert(r->ifcall.count <= 8192); /* is this guaranteed by lib9p? */
nb = r->ifcall.count;
memmove(ep->b+partial, r->ifcall.data, nb);
nb += partial;
ep->b[nb] = '\0';
if(strlen(ep->b) < nb){ /* nulls in data */
t = ep->b;
for(i=j=0; i<nb; i++)
if(ep->b[i] != '\0')
t[j++] = ep->b[i];
nb = j;
t[j] = '\0';
}
ei = nb>8192? 8192 : nb;
/* process bytes into runes, transferring terminal partial runes into next buffer */
for(i=j=0; i<ei && fullrune(ep->b+i, ei-i); i+=wid,j++)
wid = chartorune(&rune, ep->b+i);
memmove(tmp, ep->b+i, nb-i);
partial = nb-i;
ep->nb = i;
ep->nr = j;
ep->b[i] = '\0';
if(i != 0){
sendp(win->cevent, ep);
recvp(writechan);
}
partial = nb-i;
memmove(e[n]->b, tmp, partial);
r->ofcall.count = r->ifcall.count;
respond(r, nil);
}
void
fsdestroyfid(Fid *fid)
{
if(fid->aux)
free(fid->aux);
}
Srv fs = {
.read= fsread,
.write= fswrite,
.flush= fsflush,
.destroyfid= fsdestroyfid,
.leavefdsopen= 1,
};
void
mountcons(void)
{
fschan = chancreate(sizeof(Fsevent), 0);
writechan = chancreate(sizeof(void*), 0);
fs.tree = alloctree("win", "win", DMDIR|0555, nil);
devcons = createfile(fs.tree->root, "cons", "win", 0666, nil);
if(devcons == nil)
sysfatal("creating /dev/cons: %r");
devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil);
if(devnew == nil)
sysfatal("creating /dev/wnew: %r");
threadpostmountsrv(&fs, nil, "/dev", MBEFORE);
}

646
acme/bin/source/win/main.c Normal file
View File

@ -0,0 +1,646 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <ctype.h>
#include "dat.h"
void mainctl(void*);
void startcmd(char *[], int*);
void stdout2body(void*);
int debug;
int notepg;
int eraseinput;
int dirty = 0;
Window *win; /* the main window */
void
usage(void)
{
fprint(2, "usage: win [command]\n");
threadexitsall("usage");
}
void
threadmain(int argc, char *argv[])
{
int i, j;
char *dir, *tag, *name;
char buf[1024], **av;
quotefmtinstall();
rfork(RFNAMEG);
ARGBEGIN{
case 'd':
debug = 1;
chatty9p++;
break;
case 'e':
eraseinput = 1;
break;
case 'D':
{extern int _threaddebuglevel;
_threaddebuglevel = 1<<20;
}
}ARGEND
if(argc == 0){
av = emalloc(3*sizeof(char*));
av[0] = "rc";
av[1] = "-i";
name = getenv("sysname");
}else{
av = argv;
name = utfrrune(av[0], '/');
if(name)
name++;
else
name = av[0];
}
if(getwd(buf, sizeof buf) == 0)
dir = "/";
else
dir = buf;
dir = estrdup(dir);
tag = estrdup(dir);
tag = eappend(estrdup(tag), "/-", name);
win = newwindow();
snprint(buf, sizeof buf, "%d", win->id);
putenv("winid", buf);
winname(win, tag);
wintagwrite(win, "Send Noscroll", 5+8);
threadcreate(mainctl, win, STACK);
mountcons();
threadcreate(fsloop, nil, STACK);
startpipe();
startcmd(av, &notepg);
strcpy(buf, "win");
j = 3;
for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
strcpy(buf+j, " ");
strcpy(buf+j+1, argv[i]);
j += 1+strlen(argv[i]);
}
ctlprint(win->ctl, "scroll");
winsetdump(win, dir, buf);
}
int
EQUAL(char *s, char *t)
{
while(tolower(*s) == tolower(*t++))
if(*s++ == '\0')
return 1;
return 0;
}
int
command(Window *w, char *s)
{
while(*s==' ' || *s=='\t' || *s=='\n')
s++;
if(strcmp(s, "Delete")==0 || strcmp(s, "Del")==0){
windel(w, 1);
threadexitsall(nil);
return 1;
}
if(EQUAL(s, "scroll")){
ctlprint(w->ctl, "scroll\nshow");
return 1;
}
if(EQUAL(s, "noscroll")){
ctlprint(w->ctl, "noscroll");
return 1;
}
return 0;
}
static long
utfncpy(char *to, char *from, int n)
{
char *end, *e;
e = to+n;
if(to >= e)
return 0;
end = memccpy(to, from, '\0', e - to);
if(end == nil){
end = e;
if(end[-1]&0x80){
if(end-2>=to && (end[-2]&0xE0)==0xC0)
return end-to;
if(end-3>=to && (end[-3]&0xF0)==0xE0)
return end-to;
while(end>to && (*--end&0xC0)==0x80)
;
}
}else
end--;
return end - to;
}
/* sendinput and fsloop run in the same proc (can't interrupt each other). */
static Req *q;
static Req **eq;
static int
__sendinput(Window *w, ulong q0, ulong q1)
{
char *s, *t;
int n, nb, eofchar;
static int partial;
static char tmp[UTFmax];
Req *r;
Rune rune;
if(!q)
return 0;
r = q;
n = 0;
if(partial){
Partial:
nb = partial;
if(nb > r->ifcall.count)
nb = r->ifcall.count;
memmove(r->ofcall.data, tmp, nb);
if(nb!=partial)
memmove(tmp, tmp+nb, partial-nb);
partial -= nb;
q = r->aux;
if(q == nil)
eq = &q;
r->aux = nil;
r->ofcall.count = nb;
if(debug)
fprint(2, "satisfy read with partial\n");
respond(r, nil);
return n;
}
if(q0==q1)
return 0;
s = emalloc((q1-q0)*UTFmax+1);
n = winread(w, q0, q1, s);
s[n] = '\0';
t = strpbrk(s, "\n\004");
if(t == nil){
free(s);
return 0;
}
r = q;
eofchar = 0;
if(*t == '\004'){
eofchar = 1;
*t = '\0';
}else
*++t = '\0';
nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
if(nb==0 && s<t && r->ifcall.count > 0){
partial = utfncpy(tmp, s, UTFmax);
assert(partial > 0);
chartorune(&rune, tmp);
partial = runelen(rune);
free(s);
n = 1;
goto Partial;
}
n = utfnlen(r->ofcall.data, nb);
if(nb==strlen(s) && eofchar)
n++;
r->ofcall.count = nb;
q = r->aux;
if(q == nil)
eq = &q;
r->aux = nil;
if(debug)
fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
respond(r, nil);
return n;
}
static int
_sendinput(Window *w, ulong q0, ulong *q1)
{
char buf[32];
int n;
n = __sendinput(w, q0, *q1);
if(!n || !eraseinput)
return n;
/* erase q0 to q0+n */
sprint(buf, "#%lud,#%lud", q0, q0+n);
winsetaddr(w, buf, 0);
write(w->data, buf, 0);
*q1 -= n;
return 0;
}
int
sendinput(Window *w, ulong q0, ulong *q1)
{
ulong n;
Req *oq;
n = 0;
do {
oq = q;
n += _sendinput(w, q0+n, q1);
} while(q != oq);
return n;
}
Event esendinput;
void
fsloop(void*)
{
Fsevent e;
Req **l, *r;
eq = &q;
memset(&esendinput, 0, sizeof esendinput);
esendinput.c1 = 'C';
for(;;){
while(recv(fschan, &e) == -1)
;
r = e.r;
switch(e.type){
case 'r':
*eq = r;
r->aux = nil;
eq = &r->aux;
/* call sendinput with hostpt and endpt */
sendp(win->cevent, &esendinput);
break;
case 'f':
for(l=&q; *l; l=&(*l)->aux){
if(*l == r->oldreq){
*l = (*l)->aux;
if(*l == nil)
eq = l;
respond(r->oldreq, "interrupted");
break;
}
}
respond(r, nil);
break;
}
}
}
void
sendit(char *s)
{
// char tmp[32];
write(win->body, s, strlen(s));
/*
* RSC: The problem here is that other procs can call sendit,
* so we lose our single-threadedness if we call sendinput.
* In fact, we don't even have the right queue memory,
* I think that we'll get a write event from the body write above,
* and we can do the sendinput then, from our single thread.
*
* I still need to figure out how to test this assertion for
* programs that use /srv/win*
*
winselect(win, "$", 0);
seek(win->addr, 0UL, 0);
if(read(win->addr, tmp, 2*12) == 2*12)
hostpt += sendinput(win, hostpt, atol(tmp), );
*/
}
void
execevent(Window *w, Event *e, int (*command)(Window*, char*))
{
Event *ea, *e2;
int n, na, len, needfree;
char *s, *t;
ea = nil;
e2 = nil;
if(e->flag & 2)
e2 = recvp(w->cevent);
if(e->flag & 8){
ea = recvp(w->cevent);
na = ea->nb;
recvp(w->cevent);
}else
na = 0;
needfree = 0;
s = e->b;
if(e->nb==0 && (e->flag&2)){
s = e2->b;
e->q0 = e2->q0;
e->q1 = e2->q1;
e->nb = e2->nb;
}
if(e->nb==0 && e->q0<e->q1){
/* fetch data from window */
s = emalloc((e->q1-e->q0)*UTFmax+2);
n = winread(w, e->q0, e->q1, s);
s[n] = '\0';
needfree = 1;
}else
if(na){
t = emalloc(strlen(s)+1+na+2);
sprint(t, "%s %s", s, ea->b);
if(needfree)
free(s);
s = t;
needfree = 1;
}
/* if it's a known command, do it */
/* if it's a long message, it can't be for us anyway */
if(!command(w, s) && s[0]!='\0'){ /* send it as typed text */
/* if it's a built-in from the tag, send it back */
if(e->flag & 1)
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
else{ /* send text to main window */
len = strlen(s);
if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
if(!needfree){
/* if(needfree), we left room for a newline before */
t = emalloc(len+2);
strcpy(t, s);
s = t;
needfree = 1;
}
s[len++] = '\n';
s[len] = '\0';
}
sendit(s);
}
}
if(needfree)
free(s);
}
int
hasboundary(Rune *r, int nr)
{
int i;
for(i=0; i<nr; i++)
if(r[i]=='\n' || r[i]=='\004')
return 1;
return 0;
}
void
mainctl(void *v)
{
Window *w;
Event *e;
int delta, pendingS, pendingK;
ulong hostpt, endpt;
char tmp[32];
w = v;
proccreate(wineventproc, w, STACK);
hostpt = 0;
endpt = 0;
winsetaddr(w, "0", 0);
pendingS = 0;
pendingK = 0;
for(;;){
if(debug)
fprint(2, "input range %lud-%lud\n", hostpt, endpt);
e = recvp(w->cevent);
if(debug)
fprint(2, "msg: %C %C %d %d %d %d %q\n",
e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
switch(e->c1){
default:
Unknown:
fprint(2, "unknown message %c%c\n", e->c1, e->c2);
break;
case 'C': /* input needed for /dev/cons */
if(pendingS)
pendingK = 1;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
case 'S': /* output to stdout */
sprint(tmp, "#%lud", hostpt);
winsetaddr(w, tmp, 0);
write(w->data, e->b, e->nb);
pendingS += e->nr;
break;
case 'E': /* write to tag or body; body happens due to sendit */
delta = e->q1-e->q0;
if(e->c2=='I'){
endpt += delta;
if(e->q0 < hostpt)
hostpt += delta;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
}
if(!islower(e->c2))
fprint(2, "win msg: %C %C %d %d %d %d %q\n",
e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
break;
case 'F': /* generated by our actions (specifically case 'S' above) */
delta = e->q1-e->q0;
if(e->c2=='D'){
/* we know about the delete by _sendinput */
break;
}
if(e->c2=='I'){
pendingS -= e->q1 - e->q0;
if(pendingS < 0)
fprint(2, "win: pendingS = %d\n", pendingS);
if(e->q0 != hostpt)
fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
endpt += delta;
hostpt += delta;
sendp(writechan, nil);
if(pendingS == 0 && pendingK){
pendingK = 0;
hostpt += sendinput(w, hostpt, &endpt);
}
break;
}
if(!islower(e->c2))
fprint(2, "win msg: %C %C %d %d %d %d %q\n",
e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
break;
case 'K':
delta = e->q1-e->q0;
switch(e->c2){
case 'D':
endpt -= delta;
if(e->q1 < hostpt)
hostpt -= delta;
else if(e->q0 < hostpt)
hostpt = e->q0;
break;
case 'I':
delta = e->q1 - e->q0;
endpt += delta;
if(endpt < e->q1) /* just in case */
endpt = e->q1;
if(e->q0 < hostpt)
hostpt += delta;
if(e->nr>0 && e->r[e->nr-1]==0x7F){
write(notepg, "interrupt", 9);
hostpt = endpt;
break;
}
if(e->q0 >= hostpt
&& hasboundary(e->r, e->nr)){
/*
* If we are between the S message (which
* we processed by inserting text in the
* window) and the F message notifying us
* that the text has been inserted, then our
* impression of the hostpt and acme's
* may be different. This could be seen if you
* hit enter a bunch of times in a con
* session. To work around the unreliability,
* only send input if we don't have an S pending.
* The same race occurs between when a character
* is typed and when we get notice of it, but
* since characters tend to be typed at the end
* of the buffer, we don't run into it. There's
* no workaround possible for this typing race,
* since we can't tell when the user has typed
* something but we just haven't been notified.
*/
if(pendingS)
pendingK = 1;
else
hostpt += sendinput(w, hostpt, &endpt);
}
break;
}
break;
case 'M': /* mouse */
delta = e->q1-e->q0;
switch(e->c2){
case 'x':
case 'X':
execevent(w, e, command);
break;
case 'l': /* reflect all searches back to acme */
case 'L':
if(e->flag & 2)
recvp(w->cevent);
winwriteevent(w, e);
break;
case 'I':
endpt += delta;
if(e->q0 < hostpt)
hostpt += delta;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
case 'D':
endpt -= delta;
if(e->q1 < hostpt)
hostpt -= delta;
else if(e->q0 < hostpt)
hostpt = e->q0;
break;
case 'd': /* modify away; we don't care */
case 'i':
break;
default:
goto Unknown;
}
}
}
}
enum
{
NARGS = 100,
NARGCHAR = 8*1024,
EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
};
struct Exec
{
char **argv;
Channel *cpid;
};
int
lookinbin(char *s)
{
if(s[0] == '/')
return 0;
if(s[0]=='.' && s[1]=='/')
return 0;
if(s[0]=='.' && s[1]=='.' && s[2]=='/')
return 0;
return 1;
}
/* adapted from mail. not entirely free of details from that environment */
void
execproc(void *v)
{
struct Exec *e;
char *cmd, **av;
Channel *cpid;
e = v;
rfork(RFCFDG|RFNOTEG);
av = e->argv;
close(0);
open("/dev/cons", OREAD);
close(1);
open("/dev/cons", OWRITE);
dup(1, 2);
cpid = e->cpid;
free(e);
procexec(cpid, av[0], av);
if(lookinbin(av[0])){
cmd = estrstrdup("/bin/", av[0]);
procexec(cpid, cmd, av);
}
error("can't exec %s: %r", av[0]);
}
void
startcmd(char *argv[], int *notepg)
{
struct Exec *e;
Channel *cpid;
char buf[64];
int pid;
e = emalloc(sizeof(struct Exec));
e->argv = argv;
cpid = chancreate(sizeof(ulong), 0);
e->cpid = cpid;
sprint(buf, "/mnt/wsys/%d", win->id);
bind(buf, "/dev/acme", MREPL);
proccreate(execproc, e, EXECSTACK);
do
pid = recvul(cpid);
while(pid == -1);
sprint(buf, "/proc/%d/notepg", pid);
*notepg = open(buf, OWRITE);
}

View File

@ -0,0 +1,25 @@
</$objtype/mkfile
TARG=win
OFILES=\
fs.$O\
main.$O\
pipe.$O\
util.$O\
win.$O
HFILES=dat.h
LIB=/$objtype/lib/lib9p.a
BIN=/acme/bin/$objtype
</sys/src/cmd/mkone
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
${TARG:%=/acme/bin/$objtype/%}\
syms:V:
8c -a main.c >syms
8c -aa util.c win.c >>syms

175
acme/bin/source/win/pipe.c Normal file
View File

@ -0,0 +1,175 @@
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "dat.h"
typedef struct Wpid Wpid;
struct Wpid
{
int pid;
Window *w;
Wpid *next;
};
void pipectl(void*);
int pipefd;
Wpid *wpid;
int snarffd;
Channel *newpipechan;
int
newpipewin(int pid, char *p)
{
int id;
Window *w;
Wpid *wp;
w = newwindow();
winname(w, p);
wintagwrite(w, "Send ", 5);
wp = emalloc(sizeof(Wpid));
wp->pid = pid;
wp->w = w;
wp->next = wpid; /* BUG: this happens in fsread proc (we don't use wpid, so it's okay) */
wpid = wp;
id = w->id;
sendp(newpipechan, w);
return id;
}
int
pipecommand(Window *w, char *s)
{
ulong q0, q1;
char tmp[32], *t;
int n, k;
while(*s==' ' || *s=='\t' || *s=='\n')
s++;
if(strcmp(s, "Delete")==0){
windel(w, 1);
threadexits(nil);
return 1;
}
if(strcmp(s, "Del")==0){
if(windel(w, 0))
threadexits(nil);
return 1;
}
if(strcmp(s, "Send") == 0){
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
ctlprint(w->ctl, "addr=dot\n");
seek(w->addr, 0UL, 0);
if(read(w->addr, tmp, 2*12) == 2*12){
q0 = atol(tmp+0*12);
q1 = atol(tmp+1*12);
if(q0 == q1){
t = nil;
k = 0;
if(snarffd > 0){
seek(0, snarffd, 0);
for(;;){
t = realloc(t, k+8192+2);
if(t == nil)
error("alloc failed: %r\n");
n = read(snarffd, t+k, 8192);
if(n <= 0)
break;
k += n;
}
t[k] = 0;
}
}else{
t = emalloc((q1-q0)*UTFmax+2);
winread(w, q0, q1, t);
k = strlen(t);
}
if(t!=nil && t[0]!='\0'){
if(t[k-1]!='\n' && t[k-1]!='\004'){
t[k++] = '\n';
t[k] = '\0';
}
sendit(t);
}
free(t);
}
return 1;
}
return 0;
}
void
pipectl(void *v)
{
Window *w;
Event *e;
w = v;
proccreate(wineventproc, w, STACK);
windormant(w);
winsetaddr(w, "0", 0);
for(;;){
e = recvp(w->cevent);
switch(e->c1){
default:
Unknown:
fprint(2, "unknown message %c%c\n", e->c1, e->c2);
break;
case 'E': /* write to body; can't affect us */
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K': /* ignore */
break;
case 'M':
switch(e->c2){
case 'x':
case 'X':
execevent(w, e, pipecommand);
break;
case 'l': /* reflect all searches back to acme */
case 'L':
if(e->flag & 2)
recvp(w->cevent);
winwriteevent(w, e);
break;
case 'I': /* modify away; we don't care */
case 'i':
case 'D':
case 'd':
break;
default:
goto Unknown;
}
}
}
}
void
newpipethread(void*)
{
Window *w;
while(w = recvp(newpipechan))
threadcreate(pipectl, w, STACK);
}
void
startpipe(void)
{
newpipechan = chancreate(sizeof(Window*), 0);
threadcreate(newpipethread, nil, STACK);
snarffd = open("/dev/snarf", OREAD|OCEXEC);
}

View File

@ -0,0 +1,90 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "dat.h"
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
return p;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
sprint(u, "%s%s", s, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
sprint(u, "%s%s%s", s, sep, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
return s;
}
void
error(char *fmt, ...)
{
Fmt f;
char buf[64];
va_list arg;
fmtfdinit(&f, 2, buf, sizeof buf);
fmtprint(&f, "win: ");
va_start(arg, fmt);
fmtvprint(&f, fmt, arg);
va_end(arg);
fmtprint(&f, "\n");
fmtfdflush(&f);
threadexitsall(fmt);
}
void
ctlprint(int fd, char *fmt, ...)
{
int n;
va_list arg;
va_start(arg, fmt);
n = vfprint(fd, fmt, arg);
va_end(arg);
if(n <= 0)
error("control file write error: %r");
}

264
acme/bin/source/win/win.c Normal file
View File

@ -0,0 +1,264 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "dat.h"
Window*
newwindow(void)
{
char buf[12];
Window *w;
w = emalloc(sizeof(Window));
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
ctlprint(w->ctl, "noscroll\n");
w->id = atoi(buf);
w->event = winopenfile(w, "event");
w->addr = winopenfile(w, "addr");
w->body = winopenfile(w, "body");
w->data = winopenfile(w, "data");
w->cevent = chancreate(sizeof(Event*), 0);
return w;
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
}
int
winopenfile(Window *w, char *f)
{
char buf[64];
int fd;
sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
fd = open(buf, ORDWR|OCEXEC);
if(fd < 0)
error("can't open window file %s: %r", f);
return fd;
}
void
wintagwrite(Window *w, char *s, int n)
{
int fd;
fd = winopenfile(w, "tag");
if(write(fd, s, n) != n)
error("tag write: %r");
close(fd);
}
void
winname(Window *w, char *s)
{
ctlprint(w->ctl, "name %s\n", s);
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
{
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
}
void
winwriteevent(Window *w, Event *e)
{
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
static int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
int
winread(Window *w, uint q0, uint q1, char *data)
{
int m, n, nr, nb;
char buf[256];
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(w->data < 0)
w->data = winopenfile(w, "data");
m = q0;
nb = 0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
error("error writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n < 0)
error("reading data: %r");
nr = nrunes(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
nb += n;
data += n;
*data = 0;
m += nr;
}
return nb;
}
void
windormant(Window *w)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body >= 0){
close(w->body);
w->body = -1;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
int
windel(Window *w, int sure)
{
if(sure)
write(w->ctl, "delete\n", 7);
else if(write(w->ctl, "del\n", 4) != 4)
return 0;
/* event proc will die due to read error from event file */
windormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return 1;
}
void
winclean(Window *w)
{
ctlprint(w->ctl, "clean\n");
}
int
winsetaddr(Window *w, char *addr, int errok)
{
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
}
return 0;
}

3
acme/bin/unind Executable file
View File

@ -0,0 +1,3 @@
#!/bin/rc
sed 's/^ //' $*

5
acme/bin/wnew Executable file
View File

@ -0,0 +1,5 @@
#!/bin/rc -e
id=`{mkwnew $*}
cat >/mnt/acme/$id/body
echo clean >/mnt/acme/$id/ctl

4
acme/mail/guide Normal file
View File

@ -0,0 +1,4 @@
Mail stored
plumb /mail/box/$user/names
mail -'x' someaddress
mkbox /mail/box/$user/new_box

11
acme/mail/mkbox Executable file
View File

@ -0,0 +1,11 @@
#!/bin/rc
for(i){
if(! test -e $i){
if(cp /dev/null $i){
chmod 600 $i
chmod +al $i
}
}
if not echo $i already exists
}

57
acme/mail/readme Normal file
View File

@ -0,0 +1,57 @@
The Acme Mail program uses upas/fs to parse the mail box, and then
presents a file-browser-like user interface to reading and sending
messages. The Mail window presents each numbered message like the
contents of a directory presented one per line. If a message has a
Subject: line, that is shown indented on the following line.
Multipart MIME-encoded messages are presented in the obvious
hierarchical format.
Mail uses upas/fs to access the mail box. By default it reads "mbox",
the standard user mail box. If Mail is given an argument, it is
passed to upas/fs as the name of the mail box (or upas/fs directory)
to open.
Although Mail works if the plumber is not running, it's designed to be
run with plumbing enabled and many of its features work best if it is.
The mailbox window has a few commands: Put writes back the mailbox;
Mail creates a new window in which to compose a message; and Delmesg
deletes messages by number. The number may be given as argument or
indicated by selecting the header line in the mailbox window.
(Delmesg does not expand null selections, in the interest of safety.)
Clicking the right button on a message number opens it; clicking on
any of the subparts of a message opens that (and also opens the
message itself). Each message window has a few commands in the tag
with obvious names: Reply, Delmsg, etc. "Reply" replies to the single
sender of the message, "Reply all" or "Replyall" replies to everyone
in the From:, To:, and CC: lines.
Message parts with recognized MIME types such as image/jpeg are sent
to the plumber for further dispatch. Acme Mail also listens to
messages on the seemail and showmail plumbing ports, to report the
arrival of new messages (highlighting the entry; right-click on the
entry to open the message) and open them if you right-click on the
face in the faces window.
When composing a mail message or replying to a message, the first line
of the text is a list of recipients of the message. To:, and CC:, and BCC:
lines are interpreted in the usual way. Two other header lines are
special to Acme Mail:
Include: file places a copy of file in the message as an
inline MIME attachment.
Attach: file places a copy of file in the message as a regular
MIME attachment.
Acme Mail uses these conventions when replying to messages,
constructing headers for the default behavior. You may edit these to
change behavior. Most important, when replying to a message Mail will
always Include: the original message; delete that line if you don't
want to include it.
If the mailbox
/mail/box/$user/outgoing
exists, Acme Mail will save your a copy of your outgoing messages
there. Attachments are described in the copy but not included.
The -m mntpoint flag specifies a different mount point for /upas/fs.

164
acme/mail/src/dat.h Normal file
View File

@ -0,0 +1,164 @@
typedef struct Event Event;
typedef struct Exec Exec;
typedef struct Message Message;
typedef struct Window Window;
enum
{
STACK = 8192,
EVENTSIZE = 256,
NEVENT = 5,
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* file descriptors */
int ctl;
int event;
int addr;
int data;
Biobuf *body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int id;
int open;
Channel *cevent;
};
struct Message
{
Window *w;
int ctlfd;
char *name;
char *replyname;
uchar opened;
uchar dirty;
uchar isreply;
uchar deleted;
uchar writebackdel;
uchar tagposted;
uchar recursed;
uchar level;
/* header info */
char *fromcolon; /* from header file; all rest are from info file */
char *from;
char *to;
char *cc;
char *replyto;
char *date;
char *subject;
char *type;
char *disposition;
char *filename;
char *digest;
Message *next; /* next in this mailbox */
Message *prev; /* prev in this mailbox */
Message *head; /* first subpart */
Message *tail; /* last subpart */
};
enum
{
NARGS = 100,
NARGCHAR = 8*1024,
EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
};
struct Exec
{
char *prog;
char **argv;
int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
Channel *sync;
};
extern Window* newwindow(void);
extern int winopenfile(Window*, char*);
extern void winopenbody(Window*, int);
extern void winclosebody(Window*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern void winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winwritebody(Window*, char*, int);
extern void winclean(Window*);
extern int winselect(Window*, char*, int);
extern char* winselection(Window*);
extern int winsetaddr(Window*, char*, int);
extern char* winreadbody(Window*, int*);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern void readmbox(Message*, char*, char*);
extern void rewritembox(Window*, Message*);
extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
extern void delreply(Message*);
extern int mesgadd(Message*, char*, Dir*, char*);
extern void mesgmenu(Window*, Message*);
extern void mesgmenunew(Window*, Message*);
extern int mesgopen(Message*, char*, char*, Message*, int, char*);
extern void mesgctl(void*);
extern void mesgsend(Message*);
extern void mesgdel(Message*, Message*);
extern void mesgmenudel(Window*, Message*, Message*);
extern void mesgmenumark(Window*, char*, char*);
extern void mesgmenumarkdel(Window*, Message*, Message*, int);
extern Message* mesglookup(Message*, char*, char*);
extern Message* mesglookupfile(Message*, char*, char*);
extern void mesgfreeparts(Message*);
extern char* readfile(char*, char*, int*);
extern char* readbody(char*, char*, int*);
extern void ctlprint(int, char*, ...);
extern void* emalloc(uint);
extern void* erealloc(void*, uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern int tokenizec(char*, char**, int, char*);
extern void execproc(void*);
#pragma varargck argpos error 1
#pragma varargck argpos ctlprint 2
extern Window *wbox;
extern Message mbox;
extern Message replies;
extern char *fsname;
extern int plumbsendfd;
extern int plumbseemailfd;
extern char *home;
extern char *outgoing;
extern char *mailboxdir;
extern char *user;
extern char deleted[];
extern int wctlfd;
extern int shortmenu;

75
acme/mail/src/html.c Normal file
View File

@ -0,0 +1,75 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include "dat.h"
char*
formathtml(char *body, int *np)
{
int i, j, p[2], q[2];
Exec *e;
char buf[1024];
Channel *sync;
e = emalloc(sizeof(struct Exec));
if(pipe(p) < 0 || pipe(q) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->q[0] = q[0];
e->q[1] = q[1];
e->argv = emalloc(3*sizeof(char*));
e->argv[0] = estrdup("htmlfmt");
e->argv[1] = estrdup("-cutf-8");
e->argv[2] = nil;
e->prog = "/bin/htmlfmt";
sync = chancreate(sizeof(int), 0);
e->sync = sync;
proccreate(execproc, e, EXECSTACK);
recvul(sync);
close(p[0]);
close(q[1]);
if((i=write(p[1], body, *np)) != *np){
fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
close(p[1]);
close(q[0]);
return body;
}
close(p[1]);
free(body);
body = nil;
i = 0;
for(;;){
j = read(q[0], buf, sizeof buf);
if(j <= 0)
break;
body = realloc(body, i+j+1);
if(body == nil)
error("realloc failed: %r");
memmove(body+i, buf, j);
i += j;
body[i] = '\0';
}
close(q[0]);
*np = i;
return body;
}
char*
readbody(char *type, char *dir, int *np)
{
char *body;
body = readfile(dir, "body", np);
if(body != nil && strcmp(type, "text/html") == 0)
return formathtml(body, np);
return body;
}

550
acme/mail/src/mail.c Normal file
View File

@ -0,0 +1,550 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <ctype.h>
#include "dat.h"
char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
char *mailboxdir = nil; /* nil == /mail/box/$user */
char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
char *user;
char *outgoing;
Window *wbox;
Message mbox;
Message replies;
char *home;
int plumbsendfd;
int plumbseemailfd;
int plumbshowmailfd;
int plumbsendmailfd;
Channel *cplumb;
Channel *cplumbshow;
Channel *cplumbsend;
int wctlfd;
void mainctl(void*);
void plumbproc(void*);
void plumbshowproc(void*);
void plumbsendproc(void*);
void plumbthread(void);
void plumbshowthread(void*);
void plumbsendthread(void*);
int shortmenu;
void
usage(void)
{
fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
threadexitsall("usage");
}
void
removeupasfs(void)
{
char buf[256];
if(strcmp(mboxname, "mbox") == 0)
return;
snprint(buf, sizeof buf, "close %s", mboxname);
write(mbox.ctlfd, buf, strlen(buf));
}
int
ismaildir(char *s)
{
char buf[256];
Dir *d;
int ret;
snprint(buf, sizeof buf, "%s%s", maildir, s);
d = dirstat(buf);
if(d == nil)
return 0;
ret = d->qid.type & QTDIR;
free(d);
return ret;
}
void
threadmain(int argc, char *argv[])
{
char *s, *name;
char err[ERRMAX], *cmd;
int i, newdir;
Fmt fmt;
doquote = needsrcquote;
quotefmtinstall();
/* open these early so we won't miss notification of new mail messages while we read mbox */
plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
shortmenu = 0;
ARGBEGIN{
case 's':
shortmenu = 1;
break;
case 'S':
shortmenu = 2;
break;
case 'o':
outgoing = EARGF(usage());
break;
case 'm':
smprint(maildir, "%s/", EARGF(usage()));
break;
default:
usage();
}ARGEND
name = "mbox";
/* bind the terminal /mail/fs directory over the local one */
if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
bind(mailtermdir, maildir, MAFTER);
newdir = 1;
if(argc > 0){
i = strlen(argv[0]);
if(argc>2 || i==0)
usage();
/* see if the name is that of an existing /mail/fs directory */
if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
name = argv[0];
mboxname = eappend(estrdup(maildir), "", name);
newdir = 0;
}else{
if(argv[0][i-1] == '/')
argv[0][i-1] = '\0';
s = strrchr(argv[0], '/');
if(s == nil)
mboxname = estrdup(argv[0]);
else{
*s++ = '\0';
if(*s == '\0')
usage();
mailboxdir = argv[0];
mboxname = estrdup(s);
}
if(argc > 1)
name = argv[1];
else
name = mboxname;
}
}
user = getenv("user");
if(user == nil)
user = "none";
if(mailboxdir == nil)
mailboxdir = estrstrdup("/mail/box/", user);
if(outgoing == nil)
outgoing = estrstrdup(mailboxdir, "/outgoing");
s = estrstrdup(maildir, "ctl");
mbox.ctlfd = open(s, ORDWR|OCEXEC);
if(mbox.ctlfd < 0)
error("can't open %s: %r", s);
fsname = estrdup(name);
if(newdir && argc > 0){
s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
for(i=0; i<10; i++){
sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
if(write(mbox.ctlfd, s, strlen(s)) >= 0)
break;
err[0] = '\0';
errstr(err, sizeof err);
if(strstr(err, "mbox name in use") == nil)
error("can't create directory %s for mail: %s", name, err);
free(fsname);
fsname = emalloc(strlen(name)+10);
sprint(fsname, "%s-%d", name, i);
}
if(i == 10)
error("can't open %s/%s: %r", mailboxdir, mboxname);
free(s);
}
s = estrstrdup(fsname, "/");
mbox.name = estrstrdup(maildir, s);
mbox.level= 0;
readmbox(&mbox, maildir, s);
home = getenv("home");
if(home == nil)
home = "/";
wbox = newwindow();
winname(wbox, mbox.name);
wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
threadcreate(mainctl, wbox, STACK);
fmtstrinit(&fmt);
fmtprint(&fmt, "Mail");
if(shortmenu)
fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
if(outgoing)
fmtprint(&fmt, " -o %s", outgoing);
fmtprint(&fmt, " %s", name);
cmd = fmtstrflush(&fmt);
if(cmd == nil)
sysfatal("out of memory");
winsetdump(wbox, "/acme/mail", cmd);
mbox.w = wbox;
mesgmenu(wbox, &mbox);
winclean(wbox);
wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
cplumb = chancreate(sizeof(Plumbmsg*), 0);
cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
if(strcmp(name, "mbox") == 0){
/*
* Avoid creating multiple windows to send mail by only accepting
* sendmail plumb messages if we're reading the main mailbox.
*/
plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
proccreate(plumbsendproc, nil, STACK);
threadcreate(plumbsendthread, nil, STACK);
}
/* start plumb reader as separate proc ... */
proccreate(plumbproc, nil, STACK);
proccreate(plumbshowproc, nil, STACK);
threadcreate(plumbshowthread, nil, STACK);
/* ... and use this thread to read the messages */
plumbthread();
}
void
plumbproc(void*)
{
Plumbmsg *m;
threadsetname("plumbproc");
for(;;){
m = plumbrecv(plumbseemailfd);
sendp(cplumb, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbshowproc(void*)
{
Plumbmsg *m;
threadsetname("plumbshowproc");
for(;;){
m = plumbrecv(plumbshowmailfd);
sendp(cplumbshow, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbsendproc(void*)
{
Plumbmsg *m;
threadsetname("plumbsendproc");
for(;;){
m = plumbrecv(plumbsendmailfd);
sendp(cplumbsend, m);
if(m == nil)
threadexits(nil);
}
}
void
newmesg(char *name, char *digest)
{
Dir *d;
if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
return; /* message is about another mailbox */
if(mesglookupfile(&mbox, name, digest) != nil)
return;
d = dirstat(name);
if(d == nil)
return;
if(mesgadd(&mbox, mbox.name, d, digest))
mesgmenunew(wbox, &mbox);
free(d);
}
void
showmesg(char *name, char *digest)
{
char *n;
if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
return; /* message is about another mailbox */
n = estrdup(name+strlen(mbox.name));
if(n[strlen(n)-1] != '/')
n = egrow(n, "/", nil);
mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
free(n);
}
void
delmesg(char *name, char *digest, int dodel)
{
Message *m;
m = mesglookupfile(&mbox, name, digest);
if(m != nil){
mesgmenumarkdel(wbox, &mbox, m, 0);
if(dodel)
m->writebackdel = 1;
}
}
void
plumbthread(void)
{
Plumbmsg *m;
Plumbattr *a;
char *type, *digest;
threadsetname("plumbthread");
while((m = recvp(cplumb)) != nil){
a = m->attr;
digest = plumblookup(a, "digest");
type = plumblookup(a, "mailtype");
if(type == nil)
fprint(2, "Mail: plumb message with no mailtype attribute\n");
else if(strcmp(type, "new") == 0)
newmesg(m->data, digest);
else if(strcmp(type, "delete") == 0)
delmesg(m->data, digest, 0);
else
fprint(2, "Mail: unknown plumb attribute %s\n", type);
plumbfree(m);
}
threadexits(nil);
}
void
plumbshowthread(void*)
{
Plumbmsg *m;
threadsetname("plumbshowthread");
while((m = recvp(cplumbshow)) != nil){
showmesg(m->data, plumblookup(m->attr, "digest"));
plumbfree(m);
}
threadexits(nil);
}
void
plumbsendthread(void*)
{
Plumbmsg *m;
threadsetname("plumbsendthread");
while((m = recvp(cplumbsend)) != nil){
mkreply(nil, "Mail", m->data, m->attr, nil);
plumbfree(m);
}
threadexits(nil);
}
int
mboxcommand(Window *w, char *s)
{
char *args[10], **targs;
Message *m, *next;
int ok, nargs, i, j;
char buf[128];
nargs = tokenize(s, args, nelem(args));
if(nargs == 0)
return 0;
if(strcmp(args[0], "Mail") == 0){
if(nargs == 1)
mkreply(nil, "Mail", "", nil, nil);
else
mkreply(nil, "Mail", args[1], nil, nil);
return 1;
}
if(strcmp(s, "Del") == 0){
if(mbox.dirty){
mbox.dirty = 0;
fprint(2, "mail: mailbox not written\n");
return 1;
}
ok = 1;
for(m=mbox.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
for(m=replies.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
if(ok){
windel(w, 1);
removeupasfs();
threadexitsall(nil);
}
return 1;
}
if(strcmp(s, "Put") == 0){
rewritembox(wbox, &mbox);
return 1;
}
if(strcmp(s, "Delmesg") == 0){
if(nargs > 1){
for(i=1; i<nargs; i++){
snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
delmesg(buf, nil, 1);
}
}
s = winselection(w);
if(s == nil)
return 1;
nargs = 1;
for(i=0; s[i]; i++)
if(s[i] == '\n')
nargs++;
targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
nargs = getfields(s, targs, nargs, 1, "\n");
for(i=0; i<nargs; i++){
if(!isdigit(targs[i][0]))
continue;
j = atoi(targs[i]); /* easy way to parse the number! */
if(j == 0)
continue;
snprint(buf, sizeof buf, "%s%d", mbox.name, j);
delmesg(buf, nil, 1);
}
free(s);
free(targs);
return 1;
}
return 0;
}
void
mainctl(void *v)
{
Window *w;
Event *e, *e2, *eq, *ea;
int na, nopen;
char *s, *t, *buf;
w = v;
proccreate(wineventproc, w, STACK);
for(;;){
e = recvp(w->cevent);
switch(e->c1){
default:
Unknown:
print("unknown message %c%c\n", e->c1, e->c2);
break;
case 'E': /* write to body; can't affect us */
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K': /* type away; we don't care */
break;
case 'M':
switch(e->c2){
case 'x':
case 'X':
ea = nil;
e2 = nil;
if(e->flag & 2)
e2 = recvp(w->cevent);
if(e->flag & 8){
ea = recvp(w->cevent);
na = ea->nb;
recvp(w->cevent);
}else
na = 0;
s = e->b;
/* if it's a known command, do it */
if((e->flag&2) && e->nb==0)
s = e2->b;
if(na){
t = emalloc(strlen(s)+1+na+1);
sprint(t, "%s %s", s, ea->b);
s = t;
}
/* if it's a long message, it can't be for us anyway */
if(!mboxcommand(w, s)) /* send it back */
winwriteevent(w, e);
if(na)
free(s);
break;
case 'l':
case 'L':
buf = nil;
eq = e;
if(e->flag & 2){
e2 = recvp(w->cevent);
eq = e2;
}
s = eq->b;
if(eq->q1>eq->q0 && eq->nb==0){
buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
winread(w, eq->q0, eq->q1, buf);
s = buf;
}
nopen = 0;
do{
/* skip 'deleted' string if present' */
if(strncmp(s, deleted, strlen(deleted)) == 0)
s += strlen(deleted);
/* skip mail box name if present */
if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
s += strlen(mbox.name);
nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
while(*s!='\0' && *s++!='\n')
;
}while(*s);
if(nopen == 0) /* send it back */
winwriteevent(w, e);
free(buf);
break;
case 'I': /* modify away; we don't care */
case 'D':
case 'd':
case 'i':
break;
default:
goto Unknown;
}
}
}
}

1322
acme/mail/src/mesg.c Normal file

File diff suppressed because it is too large Load Diff

30
acme/mail/src/mkfile Normal file
View File

@ -0,0 +1,30 @@
</$objtype/mkfile
TARG=Mail
OFILES=\
html.$O\
mail.$O\
mesg.$O\
reply.$O\
util.$O\
win.$O
HFILES=dat.h
LIB=
BIN=/acme/bin/$objtype
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
</sys/src/cmd/mkone
$O.out: $OFILES
$LD -o $target $LDFLAGS $OFILES
syms:V:
8c -a mail.c >syms
8c -aa mesg.c reply.c util.c win.c >>syms

567
acme/mail/src/reply.c Normal file
View File

@ -0,0 +1,567 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include "dat.h"
static int replyid;
int
quote(Message *m, Biobuf *b, char *dir, char *quotetext)
{
char *body, *type;
int i, n, nlines;
char **lines;
if(quotetext){
body = quotetext;
n = strlen(body);
type = nil;
}else{
/* look for first textual component to quote */
type = readfile(dir, "type", &n);
if(type == nil){
print("no type in %s\n", dir);
return 0;
}
if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
dir = estrstrdup(dir, "1/");
if(quote(m, b, dir, nil)){
free(type);
free(dir);
return 1;
}
free(dir);
}
if(strncmp(type, "text", 4) != 0){
free(type);
return 0;
}
body = readbody(m->type, dir, &n);
if(body == nil)
return 0;
}
nlines = 0;
for(i=0; i<n; i++)
if(body[i] == '\n')
nlines++;
nlines++;
lines = emalloc(nlines*sizeof(char*));
nlines = getfields(body, lines, nlines, 0, "\n");
/* delete leading and trailing blank lines */
i = 0;
while(i<nlines && lines[i][0]=='\0')
i++;
while(i<nlines && lines[nlines-1][0]=='\0')
nlines--;
while(i < nlines){
Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
i++;
}
free(lines);
free(body); /* will free quotetext if non-nil */
free(type);
return 1;
}
void
mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
{
Message *r;
char *dir, *t;
int quotereply;
Plumbattr *a;
quotereply = (label[0] == 'Q');
r = emalloc(sizeof(Message));
r->isreply = 1;
if(m != nil)
r->replyname = estrdup(m->name);
r->next = replies.head;
r->prev = nil;
if(replies.head != nil)
replies.head->prev = r;
replies.head = r;
if(replies.tail == nil)
replies.tail = r;
r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
r->w = newwindow();
winname(r->w, r->name);
ctlprint(r->w->ctl, "cleartag");
wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
r->tagposted = 1;
threadcreate(mesgctl, r, STACK);
winopenbody(r->w, OWRITE);
if(to!=nil && to[0]!='\0')
Bprint(r->w->body, "%s\n", to);
for(a=attr; a; a=a->next)
Bprint(r->w->body, "%s: %s\n", a->name, a->value);
dir = nil;
if(m != nil){
dir = estrstrdup(mbox.name, m->name);
if(to == nil && attr == nil){
/* Reply goes to replyto; Reply all goes to From and To and CC */
if(strstr(label, "all") == nil)
Bprint(r->w->body, "To: %s\n", m->replyto);
else{ /* Replyall */
if(strlen(m->from) > 0)
Bprint(r->w->body, "To: %s\n", m->from);
if(strlen(m->to) > 0)
Bprint(r->w->body, "To: %s\n", m->to);
if(strlen(m->cc) > 0)
Bprint(r->w->body, "CC: %s\n", m->cc);
}
}
if(strlen(m->subject) > 0){
t = "Subject: Re: ";
if(strlen(m->subject) >= 3)
if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
t = "Subject: ";
Bprint(r->w->body, "%s%s\n", t, m->subject);
}
if(!quotereply){
Bprint(r->w->body, "Include: %sraw\n", dir);
free(dir);
}
}
Bprint(r->w->body, "\n");
if(m == nil)
Bprint(r->w->body, "\n");
else if(quotereply){
quote(m, r->w->body, dir, quotetext);
free(dir);
}
winclosebody(r->w);
if(m==nil && (to==nil || to[0]=='\0'))
winselect(r->w, "0", 0);
else
winselect(r->w, "$", 0);
winclean(r->w);
windormant(r->w);
}
void
delreply(Message *m)
{
if(m->next == nil)
replies.tail = m->prev;
else
m->next->prev = m->prev;
if(m->prev == nil)
replies.head = m->next;
else
m->prev->next = m->next;
mesgfreeparts(m);
free(m);
}
/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
void
buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
{
int i, n;
char *s, *a;
s = args;
for(i=0; i<NARGS; i++){
a = inargv[i];
if(a == nil)
break;
n = strlen(a)+1;
if((s-args)+n >= NARGCHAR) /* too many characters */
break;
argv[i] = s;
memmove(s, a, n);
s += n;
free(a);
}
argv[i] = nil;
}
void
execproc(void *v)
{
struct Exec *e;
int p[2], q[2];
char *prog;
char *argv[NARGS+1], args[NARGCHAR];
e = v;
p[0] = e->p[0];
p[1] = e->p[1];
q[0] = e->q[0];
q[1] = e->q[1];
prog = e->prog; /* known not to be malloc'ed */
rfork(RFFDG);
sendul(e->sync, 1);
buildargv(e->argv, argv, args);
free(e->argv);
chanfree(e->sync);
free(e);
dup(p[0], 0);
close(p[0]);
close(p[1]);
if(q[0]){
dup(q[1], 1);
close(q[0]);
close(q[1]);
}
procexec(nil, prog, argv);
//fprint(2, "exec: %s", e->prog);
//{int i;
//for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
//print("\n");
//}
//argv[0] = "cat";
//argv[1] = nil;
//procexec(nil, "/bin/cat", argv);
fprint(2, "Mail: can't exec %s: %r\n", prog);
threadexits("can't exec");
}
enum{
ATTACH,
BCC,
CC,
FROM,
INCLUDE,
TO,
};
char *headers[] = {
"attach:",
"bcc:",
"cc:",
"from:",
"include:",
"to:",
nil,
};
int
whichheader(char *h)
{
int i;
for(i=0; headers[i]!=nil; i++)
if(cistrcmp(h, headers[i]) == 0)
return i;
return -1;
}
char *tolist[200];
char *cclist[200];
char *bcclist[200];
int ncc, nbcc, nto;
char *attlist[200];
char included[200];
int
addressed(char *name)
{
int i;
for(i=0; i<nto; i++)
if(strcmp(name, tolist[i]) == 0)
return 1;
for(i=0; i<ncc; i++)
if(strcmp(name, cclist[i]) == 0)
return 1;
for(i=0; i<nbcc; i++)
if(strcmp(name, bcclist[i]) == 0)
return 1;
return 0;
}
char*
skipbl(char *s, char *e)
{
while(s < e){
if(*s!=' ' && *s!='\t' && *s!=',')
break;
s++;
}
return s;
}
char*
findbl(char *s, char *e)
{
while(s < e){
if(*s==' ' || *s=='\t' || *s==',')
break;
s++;
}
return s;
}
/*
* comma-separate possibly blank-separated strings in line; e points before newline
*/
void
commas(char *s, char *e)
{
char *t;
/* may have initial blanks */
s = skipbl(s, e);
while(s < e){
s = findbl(s, e);
if(s == e)
break;
t = skipbl(s, e);
if(t == e) /* no more words */
break;
/* patch comma */
*s++ = ',';
while(s < t)
*s++ = ' ';
}
}
int
print2(int fd, int ofd, char *fmt, ...)
{
int m, n;
char *s;
va_list arg;
va_start(arg, fmt);
s = vsmprint(fmt, arg);
va_end(arg);
if(s == nil)
return -1;
m = strlen(s);
n = write(fd, s, m);
if(ofd > 0)
write(ofd, s, m);
return n;
}
void
write2(int fd, int ofd, char *buf, int n, int nofrom)
{
char *from, *p;
int m;
write(fd, buf, n);
if(ofd <= 0)
return;
if(nofrom == 0){
write(ofd, buf, n);
return;
}
/* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
for(p=buf; *p; p+=m){
from = cistrstr(p, "from");
if(from == nil)
m = n;
else
m = from - p;
if(m > 0)
write(ofd, p, m);
if(from){
if(p==buf || from[-1]=='\n')
write(ofd, " ", 1); /* escape with space if From is at start of line */
write(ofd, from, 4);
m += 4;
}
n -= m;
}
}
void
mesgsend(Message *m)
{
char *s, *body, *to;
int i, j, h, n, natt, p[2];
struct Exec *e;
Channel *sync;
int first, nfld, delit, ofd;
char *copy, *fld[100], *now;
body = winreadbody(m->w, &n);
/* assemble to: list from first line, to: line, and cc: line */
nto = 0;
natt = 0;
ncc = 0;
nbcc = 0;
first = 1;
to = body;
for(;;){
for(s=to; *s!='\n'; s++)
if(*s == '\0'){
free(body);
return;
}
if(s++ == to) /* blank line */
break;
/* make copy of line to tokenize */
copy = emalloc(s-to);
memmove(copy, to, s-to);
copy[s-to-1] = '\0';
nfld = tokenizec(copy, fld, nelem(fld), ", \t");
if(nfld == 0){
free(copy);
break;
}
n -= s-to;
switch(h = whichheader(fld[0])){
case TO:
case FROM:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nto<nelem(tolist); i++)
if(!addressed(fld[i]))
tolist[nto++] = estrdup(fld[i]);
break;
case BCC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
if(!addressed(fld[i]))
bcclist[nbcc++] = estrdup(fld[i]);
break;
case CC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && ncc<nelem(cclist); i++)
if(!addressed(fld[i]))
cclist[ncc++] = estrdup(fld[i]);
break;
case ATTACH:
case INCLUDE:
delit = 1;
for(i=1; i<nfld && natt<nelem(attlist); i++){
attlist[natt] = estrdup(fld[i]);
included[natt++] = (h == INCLUDE);
}
break;
default:
if(first){
delit = 1;
for(i=0; i<nfld && nto<nelem(tolist); i++)
tolist[nto++] = estrdup(fld[i]);
}else /* ignore it */
delit = 0;
break;
}
if(delit){
/* delete line from body */
memmove(to, s, n+1);
}else
to = s;
free(copy);
first = 0;
}
ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
if(ofd > 0){
/* From dhog Fri Aug 24 22:13:00 EDT 2001 */
now = ctime(time(0));
fprint(ofd, "From %s %s", user, now);
fprint(ofd, "From: %s\n", user);
fprint(ofd, "Date: %s", now);
for(i=0; i<natt; i++)
if(included[i])
fprint(ofd, "Include: %s\n", attlist[i]);
else
fprint(ofd, "Attach: %s\n", attlist[i]);
/* needed because mail is by default Latin-1 */
fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
}
e = emalloc(sizeof(struct Exec));
if(pipe(p) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->prog = "/bin/upas/marshal";
e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
e->argv[0] = estrdup("marshal");
e->argv[1] = estrdup("-8");
j = 2;
if(m->replyname){
e->argv[j++] = estrdup("-R");
e->argv[j++] = estrstrdup(mbox.name, m->replyname);
}
for(i=0; i<natt; i++){
if(included[i])
e->argv[j++] = estrdup("-A");
else
e->argv[j++] = estrdup("-a");
e->argv[j++] = estrdup(attlist[i]);
}
sync = chancreate(sizeof(int), 0);
e->sync = sync;
proccreate(execproc, e, EXECSTACK);
recvul(sync);
close(p[0]);
/* using marshal -8, so generate rfc822 headers */
if(nto > 0){
print2(p[1], ofd, "To: ");
for(i=0; i<nto-1; i++)
print2(p[1], ofd, "%s, ", tolist[i]);
print2(p[1], ofd, "%s\n", tolist[i]);
}
if(ncc > 0){
print2(p[1], ofd, "CC: ");
for(i=0; i<ncc-1; i++)
print2(p[1], ofd, "%s, ", cclist[i]);
print2(p[1], ofd, "%s\n", cclist[i]);
}
if(nbcc > 0){
print2(p[1], ofd, "BCC: ");
for(i=0; i<nbcc-1; i++)
print2(p[1], ofd, "%s, ", bcclist[i]);
print2(p[1], ofd, "%s\n", bcclist[i]);
}
i = strlen(body);
if(i > 0)
write2(p[1], ofd, body, i, 1);
/* guarantee a blank line, to ensure attachments are separated from body */
if(i==0 || body[i-1]!='\n')
write2(p[1], ofd, "\n\n", 2, 0);
else if(i>1 && body[i-2]!='\n')
write2(p[1], ofd, "\n", 1, 0);
/* these look like pseudo-attachments in the "outgoing" box */
if(ofd>0 && natt>0){
for(i=0; i<natt; i++)
if(included[i])
fprint(ofd, "=====> Include: %s\n", attlist[i]);
else
fprint(ofd, "=====> Attach: %s\n", attlist[i]);
}
if(ofd > 0)
write(ofd, "\n", 1);
for(i=0; i<natt; i++)
free(attlist[i]);
close(ofd);
close(p[1]);
free(body);
if(m->replyname != nil)
mesgmenumark(mbox.w, m->replyname, "\t[replied]");
if(m->name[0] == '/')
s = estrdup(m->name);
else
s = estrstrdup(mbox.name, m->name);
s = egrow(s, "-R", nil);
winname(m->w, s);
free(s);
winclean(m->w);
/* mark message unopened because it's no longer the original message */
m->opened = 0;
}

105
acme/mail/src/util.c Normal file
View File

@ -0,0 +1,105 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include "dat.h"
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
setmalloctag(p, getcallerpc(&n));
return p;
}
void*
erealloc(void *p, uint n)
{
p = realloc(p, n);
if(p == nil)
error("can't realloc: %r");
setmalloctag(p, getcallerpc(&n));
return p;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
strcpy(u, s);
strcat(u, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
strcpy(u, s);
strcat(u, sep);
strcat(u, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
return s;
}
void
error(char *fmt, ...)
{
Fmt f;
char buf[64];
va_list arg;
fmtfdinit(&f, 2, buf, sizeof buf);
fmtprint(&f, "Mail: ");
va_start(arg, fmt);
fmtvprint(&f, fmt, arg);
va_end(arg);
fmtprint(&f, "\n");
fmtfdflush(&f);
threadexitsall(fmt);
}
void
ctlprint(int fd, char *fmt, ...)
{
int n;
va_list arg;
va_start(arg, fmt);
n = vfprint(fd, fmt, arg);
va_end(arg);
if(n <= 0)
error("control file write error: %r");
}

341
acme/mail/src/win.c Normal file
View File

@ -0,0 +1,341 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include "dat.h"
Window*
newwindow(void)
{
char buf[12];
Window *w;
w = emalloc(sizeof(Window));
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
ctlprint(w->ctl, "noscroll\n");
w->id = atoi(buf);
w->event = winopenfile(w, "event");
w->addr = -1; /* will be opened when needed */
w->body = nil;
w->data = -1;
w->cevent = chancreate(sizeof(Event*), 0);
return w;
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
}
static int
winopenfile1(Window *w, char *f, int m)
{
char buf[64];
int fd;
sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
fd = open(buf, m|OCEXEC);
if(fd < 0)
error("can't open window file %s: %r", f);
return fd;
}
int
winopenfile(Window *w, char *f)
{
return winopenfile1(w, f, ORDWR);
}
void
wintagwrite(Window *w, char *s, int n)
{
int fd;
fd = winopenfile(w, "tag");
if(write(fd, s, n) != n)
error("tag write: %r");
close(fd);
}
void
winname(Window *w, char *s)
{
ctlprint(w->ctl, "name %s\n", s);
}
void
winopenbody(Window *w, int mode)
{
char buf[256];
sprint(buf, "/mnt/wsys/%d/body", w->id);
w->body = Bopen(buf, mode|OCEXEC);
if(w->body == nil)
error("can't open window body file: %r");
}
void
winclosebody(Window *w)
{
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
}
void
winwritebody(Window *w, char *s, int n)
{
if(w->body == nil)
winopenbody(w, OWRITE);
if(Bwrite(w->body, s, n) != n)
error("write error to window: %r");
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
{
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
}
void
winwriteevent(Window *w, Event *e)
{
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
void
winread(Window *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(w->data < 0)
w->data = winopenfile(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
error("error writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n <= 0)
error("reading data: %r");
nr = utfnlen(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
windormant(Window *w)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
int
windel(Window *w, int sure)
{
if(sure)
write(w->ctl, "delete\n", 7);
else if(write(w->ctl, "del\n", 4) != 4)
return 0;
/* event proc will die due to read error from event file */
windormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return 1;
}
void
winclean(Window *w)
{
if(w->body)
Bflush(w->body);
ctlprint(w->ctl, "clean\n");
}
int
winsetaddr(Window *w, char *addr, int errok)
{
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
}
return 0;
}
char*
winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
{
char *s;
int m, na, n;
if(w->body != nil)
winclosebody(w);
winopenbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = realloc(s, na+1);
}
m = Bread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
winclosebody(w);
*np = n;
return s;
}
char*
winselection(Window *w)
{
int fd, m, n;
char *buf;
char tmp[256];
fd = winopenfile1(w, "rdsel", OREAD);
if(fd < 0)
error("can't open rdsel: %r");
n = 0;
buf = nil;
for(;;){
m = read(fd, tmp, sizeof tmp);
if(m <= 0)
break;
buf = erealloc(buf, n+m+1);
memmove(buf+n, tmp, m);
n += m;
buf[n] = '\0';
}
close(fd);
return buf;
}

10
acme/mkfile Executable file
View File

@ -0,0 +1,10 @@
</$objtype/mkfile
none:VQ:
echo mk all, install, clean, nuke, installall, update
all install clean nuke installall update:V:
@{cd bin/source; mk $target}
@{cd mail/src; mk $target}
@{cd news/src; mk $target}
@{cd wiki/src; mk $target}

2
acme/news/guide Normal file
View File

@ -0,0 +1,2 @@
Local nntpfs
News comp.os.plan9

18
acme/news/src/mkfile Normal file
View File

@ -0,0 +1,18 @@
</$objtype/mkfile
TARG=News
OFILES=\
news.$O\
util.$O\
win.$O\
HFILES=
BIN=/acme/bin/$objtype
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
${TARG:%=/acme/bin/386/%}\
</sys/src/cmd/mkone

1007
acme/news/src/news.c Normal file

File diff suppressed because it is too large Load Diff

106
acme/news/src/util.c Normal file
View File

@ -0,0 +1,106 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "win.h"
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
return p;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
strcpy(u, s);
strcat(u, t);
return u;
}
char*
estrstrstrdup(char *r, char *s, char *t)
{
char *u;
u = emalloc(strlen(r)+strlen(s)+strlen(t)+1);
strcpy(u, r);
strcat(u, s);
strcat(u, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
strcpy(u, s);
strcat(u, sep);
strcat(u, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
return s;
}
void
error(char *fmt, ...)
{
va_list arg;
char buf[256];
Fmt f;
fmtfdinit(&f, 2, buf, sizeof buf);
fmtprint(&f, "%s: ", argv0);
va_start(arg, fmt);
fmtprint(&f, fmt, arg);
va_end(arg);
fmtprint(&f, "\n");
fmtfdflush(&f);
threadexitsall(fmt);
}
void
ctlprint(int fd, char *fmt, ...)
{
int n;
va_list arg;
char buf[256];
va_start(arg, fmt);
n = vfprint(fd, fmt, arg);
va_end(arg);
if(n < 0)
error("control file write(%s) error: %r", buf);
}

324
acme/news/src/win.c Normal file
View File

@ -0,0 +1,324 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "win.h"
Window*
newwindow(void)
{
char buf[12];
Window *w;
w = emalloc(sizeof(Window));
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
ctlprint(w->ctl, "noscroll\n");
w->id = atoi(buf);
w->event = winopenfile(w, "event");
w->addr = -1; /* will be opened when needed */
w->body = nil;
w->data = -1;
w->cevent = chancreate(sizeof(Event*), 0);
if(w->cevent == nil)
error("cevent is nil: %r");
return w;
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
threadsetname("wineventproc");
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
}
int
winopenfile(Window *w, char *f)
{
char buf[64];
int fd;
sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
fd = open(buf, ORDWR|OCEXEC);
if(fd < 0)
error("can't open window file %s: %r", f);
return fd;
}
void
wintagwrite(Window *w, char *s, int n)
{
int fd;
fd = winopenfile(w, "tag");
if(write(fd, s, n) != n)
error("tag write: %r");
close(fd);
}
void
winname(Window *w, char *s)
{
ctlprint(w->ctl, "name %s\n", s);
}
void
winopenbody(Window *w, int mode)
{
char buf[256];
sprint(buf, "/mnt/wsys/%d/body", w->id);
w->body = Bopen(buf, mode|OCEXEC);
if(w->body == nil)
error("can't open window body file: %r");
}
void
winclosebody(Window *w)
{
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
}
void
winwritebody(Window *w, char *s, int n)
{
if(w->body == nil)
winopenbody(w, OWRITE);
if(Bwrite(w->body, s, n) != n)
error("write error to window: %r");
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
{
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
}
void
winwriteevent(Window *w, Event *e)
{
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
static int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
void
winread(Window *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(w->data < 0)
w->data = winopenfile(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
error("error writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n <= 0)
error("reading data: %r");
nr = nrunes(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
windormant(Window *w)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
int
windel(Window *w, int sure)
{
if(sure)
write(w->ctl, "delete\n", 7);
else if(write(w->ctl, "del\n", 4) != 4)
return 0;
/* event proc will die due to read error from event file */
windormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return 1;
}
void
winclean(Window *w)
{
if(w->body)
Bflush(w->body);
ctlprint(w->ctl, "clean\n");
}
int
winsetaddr(Window *w, char *addr, int errok)
{
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
}
return 0;
}
char*
winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
{
char *s;
int m, na, n;
if(w->body != nil)
winclosebody(w);
winopenbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = realloc(s, na+1);
}
m = Bread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
winclosebody(w);
*np = n;
return s;
}

75
acme/news/src/win.h Normal file
View File

@ -0,0 +1,75 @@
/* acme */
typedef struct Event Event;
typedef struct Window Window;
enum
{
STACK = 8192,
EVENTSIZE = 256,
NEVENT = 5,
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* file descriptors */
int ctl;
int event;
int addr;
int data;
Biobuf *body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int dirtied;
int id;
int open;
Channel *cevent; /* chan(Event*) */
};
extern Window* newwindow(void);
extern int winopenfile(Window*, char*);
extern void winopenbody(Window*, int);
extern void winclosebody(Window*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern void winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winwritebody(Window*, char*, int);
extern void winclean(Window*);
extern int winselect(Window*, char*, int);
extern int winsetaddr(Window*, char*, int);
extern char* winreadbody(Window*, int*);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern char* readfile(char*, char*, int*);
extern void ctlprint(int, char*, ...);
extern void* emalloc(uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* estrstrstrdup(char*, char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern int tokenizec(char*, char**, int, char*);

3
acme/wiki/guide Normal file
View File

@ -0,0 +1,3 @@
Local 9fs wiki
# Local wikifs /sys/lib/wiki
Wiki /mnt/wiki

114
acme/wiki/src/awiki.h Normal file
View File

@ -0,0 +1,114 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
/* acme */
typedef struct Event Event;
typedef struct Window Window;
enum
{
STACK = 8192,
EVENTSIZE = 256,
NEVENT = 5,
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* file descriptors */
int ctl;
int event;
int addr;
int data;
Biobuf *body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int warned;
int id;
int open;
Channel *cevent; /* chan(Event*) */
};
extern Window* newwindow(void);
extern int winopenfile(Window*, char*);
extern void winopenbody(Window*, int);
extern void winclosebody(Window*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern void winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winwritebody(Window*, char*, int);
extern void winclean(Window*);
extern int winisdirty(Window*);
extern int winselect(Window*, char*, int);
extern int winsetaddr(Window*, char*, int);
extern char* winreadbody(Window*, int*);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern char* readfile(char*, char*, int*);
extern void ctlprint(int, char*, ...);
extern void* emalloc(uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern int tokenizec(char*, char**, int, char*);
typedef struct Treq Treq;
typedef struct Wiki Wiki;
struct Treq {
char *title;
Channel *c; /* chan(int) */
};
struct Wiki {
QLock;
int isnew;
int special;
char *arg;
char *addr;
int n;
int dead;
Window *win;
ulong time;
int linked;
Wiki *next;
Wiki *prev;
};
extern int debug;
extern int mapfd;
extern char *email;
extern char *dir;
void wikinew(char*);
int wikiopen(char*, char*);
int wikiput(Wiki*);
void wikiget(Wiki*);
int wikidiff(Wiki*);

60
acme/wiki/src/main.c Normal file
View File

@ -0,0 +1,60 @@
#include "awiki.h"
int debug;
int mapfd;
char *email;
char *dir;
void
usage(void)
{
fprint(2, "usage: Wiki [-e email] [dir]\n");
exits("usage");
}
void
threadmain(int argc, char **argv)
{
char *s;
Dir *d;
rfork(RFNAMEG);
ARGBEGIN{
case 'D':
debug++;
break;
case 'e':
email = EARGF(usage());
break;
default:
usage();
break;
}ARGEND
if(argc > 1)
usage();
if(argc == 1)
dir = argv[0];
else
dir = "/mnt/wiki";
if(chdir(dir) < 0){
fprint(2, "chdir(%s) fails: %r\n", dir);
threadexitsall(nil);
}
if((mapfd = open("map", ORDWR)) < 0){
fprint(2, "open(map): %r\n");
threadexitsall(nil);
}
if((d = dirstat("1")) == nil){
fprint(2, "dirstat(%s/1) fails: %r\n", dir);
threadexitsall(nil);
}
s = emalloc(strlen(d->name)+2);
strcpy(s, d->name);
strcat(s, "/");
wikiopen(s, nil);
threadexits(nil);
}

14
acme/wiki/src/mkfile Normal file
View File

@ -0,0 +1,14 @@
</$objtype/mkfile
TARG=Wiki
OFILES=\
main.$O\
util.$O\
wiki.$O\
win.$O\
HFILES=awiki.h
BIN=../../bin/$objtype
</sys/src/cmd/mkone

89
acme/wiki/src/util.c Normal file
View File

@ -0,0 +1,89 @@
#include "awiki.h"
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
return p;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
strcpy(u, s);
strcat(u, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
strcpy(u, s);
strcat(u, sep);
strcat(u, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
return s;
}
void
error(char *fmt, ...)
{
int n;
va_list arg;
char buf[256];
fprint(2, "Wiki: ");
va_start(arg, fmt);
n = vseprint(buf, buf+sizeof buf, fmt, arg) - buf;
va_end(arg);
write(2, buf, n);
write(2, "\n", 1);
threadexitsall(fmt);
}
void
ctlprint(int fd, char *fmt, ...)
{
int n;
va_list arg;
char buf[256];
va_start(arg, fmt);
n = vseprint(buf, buf+sizeof buf, fmt, arg) - buf;
va_end(arg);
if(write(fd, buf, n) != n)
error("control file write(%s) error: %r", buf);
}

602
acme/wiki/src/wiki.c Normal file
View File

@ -0,0 +1,602 @@
#include "awiki.h"
Wiki *wlist;
void
link(Wiki *w)
{
if(w->linked)
return;
w->linked = 1;
w->prev = nil;
w->next = wlist;
if(wlist)
wlist->prev = w;
wlist = w;
}
void
unlink(Wiki *w)
{
if(!w->linked)
return;
w->linked = 0;
if(w->next)
w->next->prev = w->prev;
if(w->prev)
w->prev->next = w->next;
else
wlist = w->next;
w->next = nil;
w->prev = nil;
}
void
wikiname(Window *w, char *name)
{
char *p, *q;
p = emalloc(strlen(dir)+1+strlen(name)+1+1);
strcpy(p, dir);
strcat(p, "/");
strcat(p, name);
for(q=p; *q; q++)
if(*q==' ')
*q = '_';
winname(w, p);
free(p);
}
int
wikiput(Wiki *w)
{
int fd, n;
char buf[1024], *p;
Biobuf *b;
if((fd = open("new", ORDWR)) < 0){
fprint(2, "Wiki: cannot open raw: %r\n");
return -1;
}
winopenbody(w->win, OREAD);
b = w->win->body;
if((p = Brdline(b, '\n'))==nil){
Short:
winclosebody(w->win);
fprint(2, "Wiki: no data\n");
close(fd);
return -1;
}
write(fd, p, Blinelen(b));
snprint(buf, sizeof buf, "D%lud\n", w->time);
if(email)
snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "A%s\n", email);
if(Bgetc(b) == '#'){
p = Brdline(b, '\n');
if(p == nil)
goto Short;
snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "C%s\n", p);
}
write(fd, buf, strlen(buf));
write(fd, "\n\n", 2);
while((n = Bread(b, buf, sizeof buf)) > 0)
write(fd, buf, n);
winclosebody(w->win);
werrstr("");
if((n=write(fd, "", 0)) != 0){
fprint(2, "Wiki commit %lud %d %d: %r\n", w->time, fd, n);
close(fd);
return -1;
}
seek(fd, 0, 0);
if((n = read(fd, buf, 300)) < 0){
fprint(2, "Wiki readback: %r\n");
close(fd);
return -1;
}
close(fd);
buf[n] = '\0';
sprint(buf, "%s/", buf);
free(w->arg);
w->arg = estrdup(buf);
w->isnew = 0;
wikiget(w);
wikiname(w->win, w->arg);
return n;
}
void
wikiget(Wiki *w)
{
char *p;
int fd, normal;
Biobuf *bin;
fprint(w->win->ctl, "dirty\n");
p = emalloc(strlen(w->arg)+8+1);
strcpy(p, w->arg);
normal = 1;
if(p[strlen(p)-1] == '/'){
normal = 0;
strcat(p, "current");
}else if(strlen(p)>8 && strcmp(p+strlen(p)-8, "/current")==0){
normal = 0;
w->arg[strlen(w->arg)-7] = '\0';
}
if((fd = open(p, OREAD)) < 0){
fprint(2, "Wiki: cannot read %s: %r\n", p);
winclean(w->win);
return;
}
free(p);
winopenbody(w->win, OWRITE);
bin = emalloc(sizeof(*bin));
Binit(bin, fd, OREAD);
p = nil;
if(!normal){
if((p = Brdline(bin, '\n')) == nil){
fprint(2, "Wiki: cannot read title: %r\n");
winclean(w->win);
close(fd);
free(bin);
return;
}
p[Blinelen(bin)-1] = '\0';
}
/* clear window */
if(w->win->data < 0)
w->win->data = winopenfile(w->win, "data");
if(winsetaddr(w->win, ",", 0))
write(w->win->data, "", 0);
if(!normal)
Bprint(w->win->body, "%s\n\n", p);
while(p = Brdline(bin, '\n')){
p[Blinelen(bin)-1] = '\0';
if(normal)
Bprint(w->win->body, "%s\n", p);
else{
if(p[0]=='D')
w->time = strtoul(p+1, 0, 10);
else if(p[0]=='#')
Bprint(w->win->body, "%s\n", p+1);
}
}
winclean(w->win);
free(bin);
close(fd);
}
static int
iscmd(char *s, char *cmd)
{
int len;
len = strlen(cmd);
return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
}
static char*
skip(char *s, char *cmd)
{
s += strlen(cmd);
while(*s==' ' || *s=='\t' || *s=='\n')
s++;
return s;
}
int
wikiload(Wiki *w, char *arg)
{
char *p, *q, *path, *addr;
int rv;
p = nil;
if(arg[0] == '/')
path = arg;
else{
p = emalloc(strlen(w->arg)+1+strlen(arg)+1);
strcpy(p, w->arg);
if(q = strrchr(p, '/')){
++q;
*q = '\0';
}else
*p = '\0';
strcat(p, arg);
cleanname(p);
path = p;
}
if(addr=strchr(path, ':'))
*addr++ = '\0';
rv = wikiopen(path, addr)==0;
free(p);
if(rv)
return 1;
return wikiopen(arg, 0)==0;
}
/* return 1 if handled, 0 otherwise */
int
wikicmd(Wiki *w, char *s)
{
char *p;
s = skip(s, "");
if(iscmd(s, "Del")){
if(windel(w->win, 0))
w->dead = 1;
return 1;
}
if(iscmd(s, "New")){
wikinew(skip(s, "New"));
return 1;
}
if(iscmd(s, "History"))
return wikiload(w, "history.txt");
if(iscmd(s, "Diff"))
return wikidiff(w);
if(iscmd(s, "Get")){
if(winisdirty(w->win) && !w->win->warned){
w->win->warned = 1;
fprint(2, "%s/%s modified\n", dir, w->arg);
}else{
w->win->warned = 0;
wikiget(w);
}
return 1;
}
if(iscmd(s, "Put")){
if((p=strchr(w->arg, '/')) && p[1]!='\0')
fprint(2, "%s/%s is read-only\n", dir, w->arg);
else
wikiput(w);
return 1;
}
return 0;
}
/* need to expand selection more than default word */
static long
eval(Window *w, char *s, ...)
{
char buf[64];
va_list arg;
va_start(arg, s);
vsnprint(buf, sizeof buf, s, arg);
va_end(arg);
if(winsetaddr(w, buf, 1)==0)
return -1;
if(pread(w->addr, buf, 24, 0) != 24)
return -1;
return strtol(buf, 0, 10);
}
static int
getdot(Window *w, long *q0, long *q1)
{
char buf[24];
ctlprint(w->ctl, "addr=dot\n");
if(pread(w->addr, buf, 24, 0) != 24)
return -1;
*q0 = atoi(buf);
*q1 = atoi(buf+12);
return 0;
}
static Event*
expand(Window *w, Event *e, Event *eacme)
{
long q0, q1, x;
if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){
e->q0 = q0;
e->q1 = q1;
return e;
}
q0 = eval(w, "#%lud-/\\[/", e->q0);
if(q0 < 0)
return eacme;
if(eval(w, "#%lud+/\\]/", q0) < e->q0) /* [ closes before us */
return eacme;
q1 = eval(w, "#%lud+/\\]/", e->q1);
if(q1 < 0)
return eacme;
if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1) /* ] opens after us */
return eacme;
e->q0 = q0+1;
e->q1 = q1;
return e;
}
void
acmeevent(Wiki *wiki, Event *e)
{
Event *ea, *e2, *eq;
Window *w;
char *s, *t, *buf;
int na;
w = wiki->win;
switch(e->c1){ /* origin of action */
default:
Unknown:
fprint(2, "unknown message %c%c\n", e->c1, e->c2);
break;
case 'F': /* generated by our actions; ignore */
break;
case 'E': /* write to body or tag; can't affect us */
break;
case 'K': /* type away; we don't care */
if(e->c2 == 'I' || e->c2 == 'D')
w->warned = 0;
break;
case 'M': /* mouse event */
switch(e->c2){ /* type of action */
case 'x': /* mouse: button 2 in tag */
case 'X': /* mouse: button 2 in body */
ea = nil;
//e2 = nil;
s = e->b;
if(e->flag & 2){ /* null string with non-null expansion */
e2 = recvp(w->cevent);
if(e->nb==0)
s = e2->b;
}
if(e->flag & 8){ /* chorded argument */
ea = recvp(w->cevent); /* argument */
na = ea->nb;
recvp(w->cevent); /* ignore origin */
}else
na = 0;
/* append chorded arguments */
if(na){
t = emalloc(strlen(s)+1+na+1);
sprint(t, "%s %s", s, ea->b);
s = t;
}
/* if it's a known command, do it */
/* if it's a long message, it can't be for us anyway */
// DPRINT(2, "exec: %s\n", s);
if(!wikicmd(wiki, s)) /* send it back */
winwriteevent(w, e);
if(na)
free(s);
break;
case 'l': /* mouse: button 3 in tag */
case 'L': /* mouse: button 3 in body */
//buf = nil;
eq = e;
if(e->flag & 2){ /* we do our own expansion for loads */
e2 = recvp(w->cevent);
eq = expand(w, eq, e2);
}
s = eq->b;
if(eq->q1>eq->q0 && eq->nb==0){
buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
winread(w, eq->q0, eq->q1, buf);
s = buf;
}
if(!wikiload(wiki, s))
winwriteevent(w, e);
break;
case 'i': /* mouse: text inserted in tag */
case 'd': /* mouse: text deleted from tag */
break;
case 'I': /* mouse: text inserted in body */
case 'D': /* mouse: text deleted from body */
w->warned = 0;
break;
default:
goto Unknown;
}
}
}
void
wikithread(void *v)
{
char tmp[40];
Event *e;
Wiki *w;
w = v;
if(w->isnew){
sprint(tmp, "+new+%d", w->isnew);
wikiname(w->win, tmp);
if(w->arg){
winopenbody(w->win, OWRITE);
Bprint(w->win->body, "%s\n\n", w->arg);
}
winclean(w->win);
}else if(!w->special){
wikiget(w);
wikiname(w->win, w->arg);
if(w->addr)
winselect(w->win, w->addr, 1);
}
fprint(w->win->ctl, "menu\n");
wintagwrite(w->win, "Get History Diff New", 4+8+4+4);
winclean(w->win);
while(!w->dead && (e = recvp(w->win->cevent)))
acmeevent(w, e);
windormant(w->win);
unlink(w);
free(w->win);
free(w->arg);
free(w);
threadexits(nil);
}
int
wikiopen(char *arg, char *addr)
{
Dir *d;
char *p;
Wiki *w;
/*
if(arg==nil){
if(write(mapfd, title, strlen(title)) < 0
|| seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){
fprint(2, "Wiki: no page '%s' found: %r\n", title);
return -1;
}
if(tmp[n-1] == '\n')
tmp[--n] = '\0';
tmp[n++] = '/';
tmp[n] = '\0';
arg = tmp;
}
*/
/* replace embedded '\n' in links by ' ' */
for(p=arg; *p; p++)
if(*p=='\n')
*p = ' ';
if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1])
arg += strlen(dir)+1;
else if(arg[0] == '/')
return -1;
if((d = dirstat(arg)) == nil)
return -1;
if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){
p = emalloc(strlen(arg)+2);
strcpy(p, arg);
strcat(p, "/");
arg = p;
}else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){
arg = estrdup(arg);
arg[strlen(arg)-1] = '\0';
}else
arg = estrdup(arg);
free(d);
/* rewrite /current into / */
if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0)
arg[strlen(arg)-8+1] = '\0';
/* look for window already open */
for(w=wlist; w; w=w->next){
if(strcmp(w->arg, arg)==0){
ctlprint(w->win->ctl, "show\n");
return 0;
}
}
w = emalloc(sizeof *w);
w->arg = arg;
w->addr = addr;
w->win = newwindow();
link(w);
proccreate(wineventproc, w->win, STACK);
threadcreate(wikithread, w, STACK);
return 0;
}
void
wikinew(char *arg)
{
static int n;
Wiki *w;
w = emalloc(sizeof *w);
if(arg)
arg = estrdup(arg);
w->arg = arg;
w->win = newwindow();
w->isnew = ++n;
proccreate(wineventproc, w->win, STACK);
threadcreate(wikithread, w, STACK);
}
typedef struct Diffarg Diffarg;
struct Diffarg {
Wiki *w;
char *dir;
};
void
execdiff(void *v)
{
char buf[64];
Diffarg *a;
a = v;
rfork(RFFDG);
close(0);
open("/dev/null", OREAD);
sprint(buf, "/mnt/wsys/%d/body", a->w->win->id);
close(1);
open(buf, OWRITE);
close(2);
open(buf, OWRITE);
sprint(buf, "/mnt/wsys/%d", a->w->win->id);
bind(buf, "/dev", MBEFORE);
procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil);
}
int
wikidiff(Wiki *w)
{
Diffarg *d;
char *p, *q, *r;
Wiki *nw;
p = emalloc(strlen(w->arg)+10);
strcpy(p, w->arg);
if(q = strchr(p, '/'))
*q = '\0';
r = estrdup(p);
strcat(p, "/+Diff");
nw = emalloc(sizeof *w);
nw->arg = p;
nw->win = newwindow();
nw->special = 1;
d = emalloc(sizeof(*d));
d->w = nw;
d->dir = r;
wikiname(nw->win, p);
proccreate(wineventproc, nw->win, STACK);
proccreate(execdiff, d, STACK);
threadcreate(wikithread, nw, STACK);
return 1;
}

341
acme/wiki/src/win.c Normal file
View File

@ -0,0 +1,341 @@
#include "awiki.h"
Window*
newwindow(void)
{
char buf[12];
Window *w;
w = emalloc(sizeof(Window));
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
ctlprint(w->ctl, "noscroll\n");
w->id = atoi(buf);
w->event = winopenfile(w, "event");
w->addr = -1; /* will be opened when needed */
w->body = nil;
w->data = -1;
w->cevent = chancreate(sizeof(Event*), 0);
if(w->cevent == nil)
error("cevent is nil: %r");
return w;
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
threadsetname("wineventproc");
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
}
int
winopenfile(Window *w, char *f)
{
char buf[64];
int fd;
sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
fd = open(buf, ORDWR|OCEXEC);
if(fd < 0)
error("can't open window file %s: %r", f);
return fd;
}
void
wintagwrite(Window *w, char *s, int n)
{
int fd;
fd = winopenfile(w, "tag");
if(write(fd, s, n) != n)
error("tag write: %r");
close(fd);
}
void
winname(Window *w, char *s)
{
ctlprint(w->ctl, "name %s\n", s);
}
void
winopenbody(Window *w, int mode)
{
char buf[256];
sprint(buf, "/mnt/wsys/%d/body", w->id);
w->body = Bopen(buf, mode|OCEXEC);
if(w->body == nil)
error("can't open window body file: %r");
}
void
winclosebody(Window *w)
{
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
}
void
winwritebody(Window *w, char *s, int n)
{
if(w->body == nil)
winopenbody(w, OWRITE);
if(Bwrite(w->body, s, n) != n)
error("write error to window: %r");
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
{
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
}
void
winwriteevent(Window *w, Event *e)
{
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
static int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
void
winread(Window *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(w->data < 0)
w->data = winopenfile(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
error("error writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n <= 0)
error("reading data: %r");
nr = nrunes(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
windormant(Window *w)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
int
windel(Window *w, int sure)
{
if(sure)
write(w->ctl, "delete\n", 7);
else if(write(w->ctl, "del\n", 4) != 4)
return 0;
/* event proc will die due to read error from event file */
windormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return 1;
}
void
winclean(Window *w)
{
if(w->body)
Bflush(w->body);
ctlprint(w->ctl, "clean\n");
}
int
winisdirty(Window *w)
{
char m;
if (seek(w->ctl, 4*(11+1) + 10, 0) < 0)
error("control file seek error: %r");
if(read(w->ctl, &m, 1) != 1)
error("control file read error: %r");
if (m == '0')
return 0;
else if (m == '1')
return 1;
else
error("can't parse ismodified field: %c", m);
return 1; // better safe than sorry
}
int
winsetaddr(Window *w, char *addr, int errok)
{
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
}
return 0;
}
char*
winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
{
char *s;
int m, na, n;
if(w->body != nil)
winclosebody(w);
winopenbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = realloc(s, na+1);
}
m = Bread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
winclosebody(w);
*np = n;
return s;
}

27
acme/wiki/wiki.diff Executable file
View File

@ -0,0 +1,27 @@
#!/bin/rc
rfork n
cd $1
*=(`{ls -drp [0-9]*})
while(! ~ $#* 0 1){
diff -n $2/index.txt $1/index.txt | awk -F'[\/ :]' '
$1 ~/^[0-9]+$/ {
getA = "cat "$5"/current | sed -n -e ''1d; /^A/s/^A//p; /^#/q''"
getA | getline A; close getA
$1 = t2d($1)
$5 = t2d($5)
print "\n" A ":\n" $1":"$3" "$4" "$5":"$7
next
}
{ print }
function t2d(t) {
c = "date "t; c|getline l; close c
split(l, a, "[ :]+")
return a[1]" "a[2]" "a[3]" "a[4]":"a[5]" "a[8]"("t")"
}'
shift
}
echo clean >/dev/ctl >[2]/dev/null

View File

View File

View File

View File

View File

View File

View File

View File

Some files were not shown because too many files have changed in this diff Show More