ref: 77889dfbc4e6403b7fcf5d3d157d65ee7aa96488
author: Jacob Moody <moody@posixcafe.org>
date: Wed Nov 6 17:53:20 EST 2024
initial commit
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,10 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/ip
+TARG=seedbox
+OFILES=seedbox.$O
+
+</sys/src/cmd/mkone
+
+install:V: $BIN/$TARG
+ cp seedbox.1 /sys/man/1/seedbox
--- /dev/null
+++ b/seedbox.1
@@ -1,0 +1,33 @@
+.TH SEEDBOX 1
+.SH NAME
+seedbox \- bittorrent manager
+.SH SYNOPSIS
+.B ip/seedbox
+[
+.B -w
+.I seconds
+]
+.I tdir
+[
+.I ddir
+]
+.SH DESCRIPTION
+.I Seedbox
+watches
+.I tdir
+for .torrent files.
+It keeps one instance of
+.IR torrent (1)
+for each of them, stopping the instance when the
+torrent file is removed. Files will be downloaded into, or
+seeded from,
+.I ddir.
+If it is not specified,
+.I tdir
+will be used.
+The
+.B -w
+flag sets how often seedbox rescans the directory for
+changed files, the default is 30 seconds.
+.SH "SEE ALSO"
+.IR torrent (1)
--- /dev/null
+++ b/seedbox.c
@@ -1,0 +1,167 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct Seed Seed;
+struct Seed {
+ int pid;
+ char *torrent;
+ int seen;
+};
+static Seed *seeds;
+static int nseeds;
+static char *ddir, *tdir;
+static int tfd;
+
+static void
+spawn(Seed *s, char *t)
+{
+ int p;
+
+ if(t != nil && (s->torrent = strdup(t)) == nil)
+ sysfatal("strdup: %r");
+ switch(p = fork()){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ if(chdir(ddir ? ddir : tdir) < 0)
+ sysfatal("chdir: %r");
+ execl("/bin/ip/torrent", "torrent", "-s", smprint("%s/%s", tdir, s->torrent), nil);
+ sysfatal("exec: %r");
+ default:
+ s->pid = p;
+ return;
+ }
+}
+
+static void
+kill(Seed *s)
+{
+ assert(s->pid != 0);
+ postnote(PNPROC, s->pid, "kill");
+ s->pid = 0;
+ free(s->torrent);
+ s->torrent = nil;
+}
+
+static void
+scandir(void)
+{
+ Dir *d;
+ char **new, *s;
+ int i, j, n, o, p;
+
+ for(j = 0; j < nseeds; j++)
+ seeds[j].seen = 0;
+ if(seek(tfd, 0, 0) != 0)
+ sysfatal("seek: %r");
+ n = dirreadall(tfd, &d);
+ if(n < 0)
+ sysfatal("dirreadall: %r");
+ new = mallocz(sizeof(char*)*n, 1);
+ if(new == nil)
+ sysfatal("malloc: %r");
+ o = 0;
+ for(i = 0; i < n; i++){
+ if((s = strstr(d[i].name, ".torrent")) == nil)
+ continue;
+ if(s[strlen(".torrent")] != '\0')
+ continue;
+ for(j = 0; j < nseeds; j++){
+ if(strcmp(seeds[j].torrent, d[i].name) != 0)
+ continue;
+ if(seeds[j].pid == 0)
+ spawn(seeds+j, nil);
+ seeds[j].seen = 1;
+ break;
+ }
+ if(j == nseeds)
+ new[o++] = d[i].name;
+ }
+ for(j = 0; o > 0 && j < nseeds; j++){
+ if(seeds[j].seen == 1)
+ continue;
+ kill(seeds+j);
+ spawn(seeds+j, new[--o]);
+ seeds[j].seen = 1;
+ }
+ if(o > 0){
+ seeds = realloc(seeds, sizeof(Seed) * (nseeds + o));
+ if(seeds == nil)
+ sysfatal("realloc: %r");
+ for(p = o, j = nseeds; j < nseeds + o; j++){
+ seeds[j].seen = 1;
+ spawn(seeds+j, new[--p]);
+ }
+ nseeds = nseeds + o;
+ }
+ for(j = 0; j < nseeds; j++)
+ if(seeds[j].seen == 0)
+ kill(seeds+j);
+ free(new);
+ free(d);
+}
+
+static int
+catch(void*, char*)
+{
+ int j;
+
+ for(j = 0; j < nseeds; j++)
+ kill(seeds+j);
+ return 0;
+}
+
+_Noreturn void
+usage(void)
+{
+ fprint(2, "%s: tdir <ddir>\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int sec;
+ char *n, *r;
+ char buf[128];
+
+ sec = 30;
+ ARGBEGIN{
+ case 'w':
+ n = EARGF(usage());
+ sec = strtol(n, &r, 0);
+ if(n == r)
+ sysfatal("invalid number");
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ switch(argc){
+ default:
+ usage();
+ case 2:
+ ddir = argv[1];
+ case 1:
+ tfd = open(argv[0], OREAD);
+ if(tfd < 0)
+ sysfatal("open: %r");
+ if(fd2path(tfd, buf, sizeof buf) != 0)
+ sysfatal("fd2path: %r");
+ tdir = strdup(buf);
+ if(tdir == nil)
+ sysfatal("tdir: %r");
+ }
+
+ switch(fork()){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ break;
+ default:
+ exits(0);
+ }
+
+ for(atnotify(catch, 1); ; sleep(sec * 1000))
+ scandir();
+}