mirror of
git://git.9front.org/plan9front/plan9front
synced 2025-01-12 11:10:06 +00:00
fill /acme
This commit is contained in:
parent
71cfa9c637
commit
a150899221
114 changed files with 14220 additions and 0 deletions
6
acme/acid/Acid
Executable file
6
acme/acid/Acid
Executable 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
3
acme/acid/guide
Normal file
|
@ -0,0 +1,3 @@
|
|||
broke|rc kill program|rc
|
||||
Acid pid
|
||||
Acid -l thread -l acidfile pid
|
30
acme/bin/Battery
Executable file
30
acme/bin/Battery
Executable 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
13
acme/bin/Isspam
Executable 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
9
acme/bin/Mail
Executable 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
7
acme/bin/Perl
Executable 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
3
acme/bin/README
Normal 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
13
acme/bin/Spam
Executable 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
13
acme/bin/Unspam
Executable 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
24
acme/bin/adiff
Executable 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
3
acme/bin/agrep
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/rc
|
||||
|
||||
exec grep -n $* /dev/null
|
12
acme/bin/ap
Executable file
12
acme/bin/ap
Executable 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
43
acme/bin/aspell
Executable 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
4
acme/bin/guide
Normal file
|
@ -0,0 +1,4 @@
|
|||
win
|
||||
aspell file
|
||||
adiff file1 file2
|
||||
adict -d oed
|
3
acme/bin/ind
Executable file
3
acme/bin/ind
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/rc
|
||||
|
||||
sed 's/^/ /' $*
|
10
acme/bin/new
Executable file
10
acme/bin/new
Executable 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
3
acme/bin/quote
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/rc
|
||||
|
||||
sed 's/^/> /' $*
|
33
acme/bin/source/acd/README
Normal file
33
acme/bin/source/acd/README
Normal 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
243
acme/bin/source/acd/access
Normal 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
171
acme/bin/source/acd/acd.h
Normal 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
347
acme/bin/source/acd/acme.c
Normal 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
206
acme/bin/source/acd/cddb
Normal 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> <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> </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>
|
||||
|
||||
</tr></td>
|
||||
</table></tr></td></table></center></td><td> </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
197
acme/bin/source/acd/cddb.c
Normal 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);
|
||||
}
|
894
acme/bin/source/acd/cddbproto
Normal file
894
acme/bin/source/acd/cddbproto
Normal 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> <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> </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:
|
||||
-> : client to server
|
||||
<- : 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:
|
||||
----------------------
|
||||
<- 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:
|
||||
-> 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:
|
||||
<- 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:
|
||||
-> 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:
|
||||
<- code categ discid dtitle
|
||||
or
|
||||
<- code close matches found
|
||||
<- categ discid dtitle
|
||||
<- categ discid dtitle
|
||||
<- (more matches...)
|
||||
<- .
|
||||
|
||||
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:
|
||||
-> cddb read categ discid
|
||||
|
||||
categ:
|
||||
CD category. Example: rock
|
||||
discid:
|
||||
CD disc ID number. Example: f50a3b13
|
||||
|
||||
Server response:
|
||||
<- code categ discid
|
||||
<- # xmcd 2.0 CD database file
|
||||
<- # ...
|
||||
<- (CDDB data...)
|
||||
<- .
|
||||
or
|
||||
<- 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:
|
||||
-> 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:
|
||||
<- code matches found
|
||||
<- categ discid dtitle
|
||||
<- categ discid dtitle
|
||||
<- (more matches...)
|
||||
<- .
|
||||
|
||||
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:
|
||||
-> cddb write categ discid
|
||||
|
||||
categ:
|
||||
CD category. Example: rock
|
||||
discid:
|
||||
CD disc ID number. Example: f50a3b13
|
||||
|
||||
Server response:
|
||||
<- 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:
|
||||
-> # xmcd 2.0 CD database file
|
||||
-> # ...
|
||||
-> (CDDB data)
|
||||
-> .
|
||||
|
||||
Server response:
|
||||
<- 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:
|
||||
-> help
|
||||
or
|
||||
-> help cmd
|
||||
|
||||
cmd:
|
||||
CDDB command. Example: quit
|
||||
|
||||
or
|
||||
|
||||
-> help cmd subcmd
|
||||
|
||||
cmd:
|
||||
CDDB command. Example: cddb
|
||||
subcmd:
|
||||
CDDB command argument. Example: query
|
||||
|
||||
Server response:
|
||||
<- code Help information follows
|
||||
<- (help data ...)
|
||||
<- .
|
||||
or
|
||||
<- code no help information available
|
||||
|
||||
code:
|
||||
210 OK, help information follows (until terminating marker)
|
||||
401 No help information available
|
||||
|
||||
|
||||
Log statistics:
|
||||
---------------
|
||||
Client command:
|
||||
-> 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:
|
||||
<- code Log summary follows
|
||||
<- (log stats)
|
||||
<- .
|
||||
or
|
||||
<- code Log follows
|
||||
<- (log stats)
|
||||
<- .
|
||||
|
||||
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:
|
||||
-> motd
|
||||
|
||||
Server response:
|
||||
<- code Last modified: date MOTD follows (until terminating marker)
|
||||
<- (message text)
|
||||
<- .
|
||||
|
||||
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:
|
||||
-> proto [level]
|
||||
|
||||
level:
|
||||
The (numerical) protocol level to set the server to.
|
||||
|
||||
Server response:
|
||||
<- code CDDB protocol level: current cur_level, supported supported_level
|
||||
or
|
||||
<- 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:
|
||||
-> sites
|
||||
|
||||
Server response:
|
||||
<- code OK, site information follows (until terminating `.')
|
||||
<- (data)
|
||||
<- .
|
||||
|
||||
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:
|
||||
-> stat
|
||||
|
||||
Server response:
|
||||
<- code OK, status information follows (until terminating `.')
|
||||
<- (data)
|
||||
<- .
|
||||
|
||||
code:
|
||||
210 Ok, status information follows
|
||||
|
||||
The possible data is as follows:
|
||||
current proto: <current_level>
|
||||
An integer representing the server's current operating protocol
|
||||
level.
|
||||
max proto: <max_level>
|
||||
The maximum supported protocol level.
|
||||
gets: <yes | no>
|
||||
Whether or not the client is allowed to get log information,
|
||||
according to the string "yes" or "no".
|
||||
updates: <yes | no>
|
||||
Whether or not the client is allowed to initiate a database
|
||||
update, according to the string "yes" or "no".
|
||||
posting: <yes | no>
|
||||
Whether or not the client is allowed to post new entries,
|
||||
according to the string "yes" or "no".
|
||||
quotes: <yes | no>
|
||||
Whether or not quoted arguments are enabled, according to
|
||||
the string "yes" or "no".
|
||||
current users: <num_users>
|
||||
The number of users currently connected to the server.
|
||||
max users: <num_max_users>
|
||||
The number of users that can concurrently connect to the server.
|
||||
strip ext: <yes | no>
|
||||
Whether or not extended data is stripped by the server before
|
||||
presented to the user.
|
||||
Database entries: <num_db_entries>
|
||||
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:
|
||||
|
||||
<white space>catgory: <num_db_entries>
|
||||
|
||||
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:
|
||||
|
||||
<white space>site: <num_db_entries>
|
||||
|
||||
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:
|
||||
-> ver
|
||||
|
||||
Server response:
|
||||
<- code servername version copyright
|
||||
or
|
||||
<- 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:
|
||||
-> update
|
||||
|
||||
Server response:
|
||||
<- code Updating the database.
|
||||
or
|
||||
<- code Permission denied.
|
||||
or
|
||||
<- code Unable to update the database.
|
||||
|
||||
code:
|
||||
200 Updating the database.
|
||||
401 Permission denied.
|
||||
402 Unable to update the database.
|
||||
|
||||
|
||||
Server users:
|
||||
-------------
|
||||
Client command:
|
||||
-> whom
|
||||
|
||||
Server response:
|
||||
<- code User list follows
|
||||
|
||||
code:
|
||||
210 OK, user list follows (until terminating marker)
|
||||
401 No user information available.
|
||||
|
||||
|
||||
Client sign-off:
|
||||
----------------
|
||||
Client command:
|
||||
-> quit
|
||||
|
||||
Server response:
|
||||
<- code hostname closing connection. Goodbye.
|
||||
|
||||
code:
|
||||
230 OK, goodbye.
|
||||
hostname:
|
||||
Server host name. Example: xyz.fubar.com
|
||||
|
||||
|
||||
General errors:
|
||||
---------------
|
||||
|
||||
Server response:
|
||||
<- 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&hello=joe+my.host.com+clientname+version&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
|
||||
"&" 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&hello=joe+my.host.com+xmcd+2.1&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>
|
||||
|
||||
</tr></td>
|
||||
</table></tr></td></table></center></td><td> </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
159
acme/bin/source/acd/discid
Normal 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.
|
220
acme/bin/source/acd/mailinglist
Normal file
220
acme/bin/source/acd/mailinglist
Normal 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> <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> </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>
|
||||
|
||||
</tr></td>
|
||||
</table></tr></td></table></center></td><td> </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
135
acme/bin/source/acd/main.c
Normal 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);
|
||||
}
|
22
acme/bin/source/acd/mkfile
Normal file
22
acme/bin/source/acd/mkfile
Normal 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
303
acme/bin/source/acd/mmc.c
Normal 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);
|
||||
}
|
||||
}
|
32
acme/bin/source/acd/outline
Normal file
32
acme/bin/source/acd/outline
Normal 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
220
acme/bin/source/acd/submit
Normal 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
59
acme/bin/source/acd/toc.c
Normal 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);
|
||||
|
||||
}
|
89
acme/bin/source/acd/util.c
Normal file
89
acme/bin/source/acd/util.c
Normal 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
320
acme/bin/source/acd/win.c
Normal 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;
|
||||
}
|
584
acme/bin/source/adict/_adict.c
Normal file
584
acme/bin/source/adict/_adict.c
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
315
acme/bin/source/adict/_win.c
Normal file
315
acme/bin/source/adict/_win.c
Normal 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;
|
||||
}
|
591
acme/bin/source/adict/adict.c
Normal file
591
acme/bin/source/adict/adict.c
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
10
acme/bin/source/adict/adict.h
Normal file
10
acme/bin/source/adict/adict.h
Normal 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
26
acme/bin/source/adict/man
Normal 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.
|
11
acme/bin/source/adict/mkfile
Normal file
11
acme/bin/source/adict/mkfile
Normal 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
315
acme/bin/source/adict/win.c
Normal 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;
|
||||
}
|
59
acme/bin/source/adict/win.h
Normal file
59
acme/bin/source/adict/win.h
Normal 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
27
acme/bin/source/mkfile
Normal 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
45
acme/bin/source/mkwnew.c
Normal 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
123
acme/bin/source/spout.c
Normal 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
146
acme/bin/source/win/_fs.c
Normal 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
651
acme/bin/source/win/_main.c
Normal 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, ¬epg);
|
||||
|
||||
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
95
acme/bin/source/win/dat.h
Normal 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
147
acme/bin/source/win/fs.c
Normal 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
646
acme/bin/source/win/main.c
Normal 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, ¬epg);
|
||||
|
||||
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);
|
||||
}
|
25
acme/bin/source/win/mkfile
Normal file
25
acme/bin/source/win/mkfile
Normal 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
175
acme/bin/source/win/pipe.c
Normal 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);
|
||||
}
|
90
acme/bin/source/win/util.c
Normal file
90
acme/bin/source/win/util.c
Normal 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
264
acme/bin/source/win/win.c
Normal 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
3
acme/bin/unind
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/rc
|
||||
|
||||
sed 's/^ //' $*
|
5
acme/bin/wnew
Executable file
5
acme/bin/wnew
Executable 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
4
acme/mail/guide
Normal 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
11
acme/mail/mkbox
Executable 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
57
acme/mail/readme
Normal 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
164
acme/mail/src/dat.h
Normal 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
75
acme/mail/src/html.c
Normal 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
550
acme/mail/src/mail.c
Normal 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
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
30
acme/mail/src/mkfile
Normal 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
567
acme/mail/src/reply.c
Normal 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
105
acme/mail/src/util.c
Normal 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
341
acme/mail/src/win.c
Normal 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
10
acme/mkfile
Executable 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
2
acme/news/guide
Normal file
|
@ -0,0 +1,2 @@
|
|||
Local nntpfs
|
||||
News comp.os.plan9
|
18
acme/news/src/mkfile
Normal file
18
acme/news/src/mkfile
Normal 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
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
106
acme/news/src/util.c
Normal 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
324
acme/news/src/win.c
Normal 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
75
acme/news/src/win.h
Normal 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
3
acme/wiki/guide
Normal file
|
@ -0,0 +1,3 @@
|
|||
Local 9fs wiki
|
||||
# Local wikifs /sys/lib/wiki
|
||||
Wiki /mnt/wiki
|
114
acme/wiki/src/awiki.h
Normal file
114
acme/wiki/src/awiki.h
Normal 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
60
acme/wiki/src/main.c
Normal 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
14
acme/wiki/src/mkfile
Normal 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
89
acme/wiki/src/util.c
Normal 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
602
acme/wiki/src/wiki.c
Normal 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
341
acme/wiki/src/win.c
Normal 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
27
acme/wiki/wiki.diff
Executable 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
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue