@@ -37,6 +37,10 @@ handler.setFormatter(logging.Formatter(
3737log .addHandler (handler )
3838
3939
40+ ################
41+ # Util helpers #
42+ ################
43+
4044def to_encoding (ustring ):
4145 if isinstance (ustring , basestring ):
4246 if isinstance (ustring , unicode ):
@@ -45,6 +49,27 @@ def to_encoding(ustring):
4549 return u''
4650
4751
52+ def open_without_clobber (name , * args ):
53+ '''Try to open the given file with the given mode; if that filename exists,
54+ try "name.1", "name.2", etc. until we find an unused filename.'''
55+ fd = None
56+ count = 1
57+ orig_name = name
58+ while fd is None :
59+ try :
60+ fd = os .open (name , os .O_CREAT | os .O_EXCL , 0666 )
61+ except OSError as e :
62+ if e .errno == os .errno .EEXIST :
63+ name = "%s.%i" % (orig_name , count )
64+ count += 1
65+ else : # raise IOError like regular open()
66+ raise IOError , (e .errno , e .strerror , e .filename )
67+ fobj = open (name , * args )
68+ if fd != fobj .fileno ():
69+ os .close (fd )
70+ return fobj
71+
72+
4873##################
4974# Option parsing #
5075##################
@@ -79,6 +104,7 @@ def setup_parser():
79104
80105 return p
81106
107+
82108def setup_action_parser (action ):
83109 p = optparse .OptionParser (usage = "%%prog %s [options]" % action )
84110
@@ -230,7 +256,8 @@ def setup_action_parser(action):
230256 help = 'Add an email to the cc list' )
231257 p .add_option ('-F' ,'--fixed_in' ,metavar = "VERSION" ,
232258 help = '"Fixed in version" field' )
233- # TODO: --keyword, --tag, --cc, ...
259+ # TODO: --keyword, --tag, ...
260+
234261 elif action == 'attach' :
235262 p .set_usage ('''
236263 %prog attach --file=FILE --desc=DESC [--type=TYPE] BUGID [BUGID...]
@@ -247,6 +274,7 @@ def setup_action_parser(action):
247274 default = [], help = "Download the attachment with the given ID" )
248275 p .add_option ("--getall" ,"--get-all" , metavar = "BUGID" , action = "append" ,
249276 default = [], help = "Download all attachments on the given bug" )
277+
250278 elif action == 'login' :
251279 p .set_usage ('%prog login [username [password]]' )
252280 p .set_description ("Log into bugzilla and save a login cookie." )
@@ -718,6 +746,66 @@ def _do_modify(bz, opt, args):
718746 bz ._update_bugs (bugid_list , data )
719747
720748
749+ def _do_attach (bz , opt , parser , args ):
750+ # Getting attachments
751+ if opt .get or opt .getall :
752+ if args :
753+ parser .error ("Extra args '%s' not used for getting attachments" %
754+ args )
755+
756+ for bug in bz .getbugs (opt .getall ):
757+ opt .get += [a ['attach_id' ] for a in bug .attachments ]
758+
759+ for attid in set (opt .get ):
760+ att = bz .openattachment (attid )
761+ outfile = open_without_clobber (att .name , "wb" )
762+ data = att .read (4096 )
763+ while data :
764+ outfile .write (data )
765+ data = att .read (4096 )
766+ print "Wrote %s" % outfile .name
767+
768+ return
769+
770+ # Setting attachments
771+ if not args :
772+ parser .error ("Bug ID must be specified for setting attachments" )
773+
774+ if not opt .type :
775+ try :
776+ import magic
777+ except ImportError :
778+ RuntimeError ("specify --type or install python-magic" )
779+ mimemagic = magic .open (magic .MAGIC_MIME_TYPE )
780+ mimemagic .load ()
781+
782+ if sys .stdin .isatty ():
783+ # stdin is a tty -> normal CLI
784+ if not opt .type :
785+ opt .type = mimemagic .file (opt .file )
786+ fileobj = open (opt .file )
787+
788+ else :
789+ # piped input on stdin
790+ # write it to a tempfile - and get the filetype if we need it
791+ fileobj = NamedTemporaryFile (prefix = "bugzilla-attach." )
792+ data = sys .stdin .read (4096 )
793+ if not opt .type :
794+ opt .type = mimemagic .buffer (data )
795+ while data :
796+ fileobj .write (data )
797+ data = sys .stdin .read (4096 )
798+ fileobj .seek (0 )
799+
800+ # Upload attachments
801+ for bugid in args :
802+ attid = bz .attachfile (bugid , fileobj , opt .desc ,
803+ filename = opt .file ,
804+ contenttype = opt .type ,
805+ ispatch = (opt .type == "text/x-patch" ))
806+ print "Created attachment %i on bug %s" % (attid , bugid )
807+
808+
721809#################
722810# Main function #
723811#################
@@ -845,47 +933,10 @@ def main(bzinstance=None):
845933 buglist += newbugs
846934
847935 elif action == 'attach' :
848- if opt .get or opt .getall :
849- for bug in bz .getbugs (opt .getall ):
850- opt .get += [a ['attach_id' ] for a in bug .attachments ]
851- for attid in set (opt .get ):
852- att = bz .openattachment (attid )
853- outfile = bugzilla .util .open_without_clobber (att .name , "wb" )
854- data = att .read (4096 )
855- while data :
856- outfile .write (data )
857- data = att .read (4096 )
858- print "Wrote %s" % outfile .name
859- return
860936 if not opt .file and opt .desc :
861- parser .error ("attaching a file requires --file and --desc" )
862- if not opt .type :
863- try :
864- import magic
865- except ImportError :
866- parser .error ("specify --type or install python-magic" )
867- mimemagic = magic .open (magic .MAGIC_MIME_TYPE )
868- mimemagic .load ()
869- if sys .stdin .isatty (): # stdin is a tty -> normal CLI
870- if not opt .type :
871- opt .type = mimemagic .file (opt .file )
872- fileobj = open (opt .file )
873- else : # piped input on stdin
874- # write it to a tempfile - and get the filetype if we need it
875- fileobj = NamedTemporaryFile (prefix = "bugzilla-attach." )
876- data = sys .stdin .read (4096 )
877- if not opt .type :
878- opt .type = mimemagic .buffer (data )
879- while data :
880- fileobj .write (data )
881- data = sys .stdin .read (4096 )
882- fileobj .seek (0 )
883- for bugid in args :
884- attid = bz .attachfile (bugid , fileobj , opt .desc ,
885- filename = opt .file ,
886- contenttype = opt .type ,
887- ispatch = (opt .type == "text/x-patch" ))
888- print "Created attachment %i on bug %s" % (attid , bugid )
937+ parser .error ("attaching a file requires --file and --description" )
938+
939+ _do_attach (bz , opt , parser , args )
889940
890941 elif action == 'modify' :
891942 if opt .dupeid is not None :
0 commit comments