]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/magic_folder_cli.py
Teach magic-folder join to use argv_to_abspath
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / magic_folder_cli.py
1
2 import os
3 from cStringIO import StringIO
4 from twisted.python import usage
5
6 from .common import BaseOptions, BasedirOptions, get_aliases
7 from .cli import MakeDirectoryOptions, LnOptions, CreateAliasOptions
8 import tahoe_mv
9 from allmydata.util.encodingutil import argv_to_abspath
10 from allmydata.util import fileutil
11 from allmydata import uri
12
13 INVITE_SEPARATOR = "+"
14
15 class CreateOptions(BasedirOptions):
16     nickname = None
17     localdir = None
18     synopsis = "MAGIC_ALIAS: [NICKNAME LOCALDIR]"
19     def parseArgs(self, alias, nickname=None, localdir=None):
20         BasedirOptions.parseArgs(self)
21         if not alias.endswith(':'):
22             raise usage.UsageError("An alias must end with a ':' character.")
23         self.alias = alias[:-1]
24         self.nickname = nickname
25         self.localdir = argv_to_abspath(str(localdir))
26         if self.nickname and not self.localdir:
27             raise usage.UsageError("If NICKNAME is specified then LOCALDIR must also be specified.")
28         node_url_file = os.path.join(self['node-directory'], "node.url")
29         self['node-url'] = fileutil.read(node_url_file).strip()
30
31 def _delegate_options(source_options, target_options):
32     target_options.aliases = get_aliases(source_options['node-directory'])
33     target_options["node-url"] = source_options["node-url"]
34     target_options["node-directory"] = source_options["node-directory"]
35     target_options.stdin = StringIO("")
36     target_options.stdout = StringIO()
37     target_options.stderr = StringIO()
38     return target_options
39
40 def create(options):
41     from allmydata.scripts import tahoe_add_alias
42     create_alias_options = _delegate_options(options, CreateAliasOptions())
43     create_alias_options.alias = options.alias
44
45     rc = tahoe_add_alias.create_alias(create_alias_options)
46     if rc != 0:
47         print >>options.stderr, create_alias_options.stderr.getvalue()
48         return rc
49     print >>options.stdout, create_alias_options.stdout.getvalue()
50
51     if options.nickname is not None:
52         invite_options = _delegate_options(options, InviteOptions())
53         invite_options.alias = options.alias
54         invite_options.nickname = options.nickname
55         rc = invite(invite_options)
56         if rc != 0:
57             print >>options.stderr, "magic-folder: failed to invite after create\n"
58             print >>options.stderr, invite_options.stderr.getvalue()
59             return rc
60         invite_code = invite_options.stdout.getvalue().strip()
61
62         join_options = _delegate_options(options, JoinOptions())
63         join_options.invite_code = invite_code
64         fields = invite_code.split(INVITE_SEPARATOR)
65         if len(fields) != 2:
66             raise usage.UsageError("Invalid invite code.")
67         join_options.magic_readonly_cap, join_options.dmd_write_cap = fields
68         join_options.local_dir = options.localdir
69         rc = join(join_options)
70         if rc != 0:
71             print >>options.stderr, "magic-folder: failed to join after create\n"
72             print >>options.stderr, join_options.stderr.getvalue()
73             return rc
74     return 0
75
76 class InviteOptions(BasedirOptions):
77     nickname = None
78     synopsis = "MAGIC_ALIAS: NICKNAME"
79     stdin = StringIO("")
80     def parseArgs(self, alias, nickname=None):
81         BasedirOptions.parseArgs(self)
82         if not alias.endswith(':'):
83             raise usage.UsageError("An alias must end with a ':' character.")
84         self.alias = alias[:-1]
85         self.nickname = nickname
86         node_url_file = os.path.join(self['node-directory'], "node.url")
87         self['node-url'] = open(node_url_file, "r").read().strip()
88         aliases = get_aliases(self['node-directory'])
89         self.aliases = aliases
90
91 def invite(options):
92     from allmydata.scripts import tahoe_mkdir
93     mkdir_options = _delegate_options(options, MakeDirectoryOptions())
94     mkdir_options.where = None
95
96     rc = tahoe_mkdir.mkdir(mkdir_options)
97     if rc != 0:
98         print >>options.stderr, "magic-folder: failed to mkdir\n"
99         return rc
100     dmd_write_cap = mkdir_options.stdout.getvalue().strip()
101     dmd_readonly_cap = unicode(uri.from_string(dmd_write_cap).get_readonly().to_string(), 'utf-8')
102     if dmd_readonly_cap is None:
103         print >>options.stderr, "magic-folder: failed to diminish dmd write cap\n"
104         return 1
105
106     magic_write_cap = get_aliases(options["node-directory"])[options.alias]
107     magic_readonly_cap = unicode(uri.from_string(magic_write_cap).get_readonly().to_string(), 'utf-8')
108     # tahoe ln CLIENT_READCAP COLLECTIVE_WRITECAP/NICKNAME
109     ln_options = _delegate_options(options, LnOptions())
110     ln_options.from_file = dmd_readonly_cap
111     ln_options.to_file = u"%s/%s" % (magic_write_cap, options.nickname)
112     rc = tahoe_mv.mv(ln_options, mode="link")
113     if rc != 0:
114         print >>options.stderr, "magic-folder: failed to create link\n"
115         print >>options.stderr, ln_options.stderr.getvalue()
116         return rc
117
118     print >>options.stdout, "%s%s%s" % (magic_readonly_cap, INVITE_SEPARATOR, dmd_write_cap)
119     return 0
120
121 class JoinOptions(BasedirOptions):
122     synopsis = "INVITE_CODE LOCAL_DIR"
123     dmd_write_cap = ""
124     magic_readonly_cap = ""
125     def parseArgs(self, invite_code, local_dir):
126         BasedirOptions.parseArgs(self)
127         self.local_dir = argv_to_abspath(str(local_dir))
128         fields = invite_code.split(INVITE_SEPARATOR)
129         if len(fields) != 2:
130             raise usage.UsageError("Invalid invite code.")
131         self.magic_readonly_cap, self.dmd_write_cap = fields
132
133 def join(options):
134     dmd_cap_file = os.path.join(options["node-directory"], "private/magic_folder_dircap")
135     collective_readcap_file = os.path.join(options["node-directory"], "private/collective_dircap")
136
137     fileutil.write(dmd_cap_file, options.dmd_write_cap)
138     fileutil.write(collective_readcap_file, options.magic_readonly_cap)
139     fileutil.write(os.path.join(options["node-directory"], "tahoe.cfg"),
140                    "[magic_folder]\nenabled = True\nlocal.directory = %s\n"
141                    % (options.local_dir.encode('utf-8'),), mode="ab")
142     return 0
143
144 class MagicFolderCommand(BaseOptions):
145     subCommands = [
146         ["create", None, CreateOptions, "Create a Magic Folder."],
147         ["invite", None, InviteOptions, "Invite someone to a Magic Folder."],
148         ["join", None, JoinOptions, "Join a Magic Folder."],
149     ]
150     def postOptions(self):
151         if not hasattr(self, 'subOptions'):
152             raise usage.UsageError("must specify a subcommand")
153     def getSynopsis(self):
154         return "Usage: tahoe [global-options] magic SUBCOMMAND"
155     def getUsage(self, width=None):
156         t = BaseOptions.getUsage(self, width)
157         t += """\
158 Please run e.g. 'tahoe magic-folder create --help' for more details on each
159 subcommand.
160 """
161         return t
162
163 subDispatch = {
164     "create": create,
165     "invite": invite,
166     "join": join,
167 }
168
169 def do_magic_folder(options):
170     so = options.subOptions
171     so.stdout = options.stdout
172     so.stderr = options.stderr
173     f = subDispatch[options.subCommand]
174     return f(so)
175
176 subCommands = [
177     ["magic-folder", None, MagicFolderCommand,
178      "Magic Folder subcommands: use 'tahoe magic-folder' for a list."],
179 ]
180
181 dispatch = {
182     "magic-folder": do_magic_folder,
183 }