Browse Source

end dev upload and download

fraoustin 5 years ago
parent
commit
0e95c6af1f

+ 1
- 1
AUTHORS.txt View File

@@ -1 +1 @@
1
-Name::name@mail.com
1
+Frédéric Aoustin::fraoustin@gmail.com

+ 11
- 0
CHANGES.rst View File

@@ -0,0 +1,11 @@
1
+0.0.2
2
+=====
3
+
4
+- add verb upload, download and ws
5
+- integrate progressbar
6
+- manage thread
7
+
8
+0.0.1
9
+=====
10
+
11
+init

+ 58
- 1
README.rst View File

@@ -1 +1,58 @@
1
-piwigotools
1
+piwigotools
2
+===========
3
+
4
+Piwigo is a famous open-source online photo gallery. 
5
+
6
+Piwigotools is a module python for manage your piwigo gallery.
7
+The module add command "piwigo"
8
+
9
+
10
+Installation
11
+------------
12
+
13
+Warning
14
+~~~~~~~
15
+
16
+Piwigotools needs the progressbar module.
17
+
18
+But progressbar moduel is not compatible with python3
19
+
20
+install for python2.7
21
+
22
+::
23
+
24
+    pip install progressbar
25
+
26
+install for python 3
27
+
28
+::
29
+
30
+    git clone https://github.com/coagulant/progressbar-python3.git
31
+    cd progressbar-python3
32
+    python setup.py install
33
+
34
+
35
+Install
36
+~~~~~~~
37
+
38
+
39
+::
40
+
41
+    pip install piwigotools
42
+        
43
+Or
44
+
45
+::
46
+
47
+    git clone https://github.com/fraoustin/piwigotools.git
48
+    cd piwigotools
49
+    python setup.py install
50
+
51
+Usage
52
+-----
53
+
54
+::
55
+
56
+    piwigo verb --param1=value1 --param2=value2 ...
57
+
58
+

+ 1
- 0
REQUIREMENTS.txt View File

@@ -0,0 +1 @@
1
+piwigo

+ 223
- 1
piwigotools/__init__.py View File

@@ -5,5 +5,227 @@
5 5
     Module piwigotools
6 6
 """
7 7
 
8
-__version_info__ = (0, 0, 1)
8
+__version_info__ = (0, 0, 2)
9 9
 __version__ = '.'.join([str(val) for val in  __version_info__])
10
+
11
+import inspect
12
+
13
+import requests
14
+
15
+import piwigo
16
+
17
+class LoginException(Exception):
18
+
19
+    def __str__(self):
20
+        return "You are not logged"
21
+
22
+class PiwigoException(Exception):
23
+    
24
+    def __init__(self, strerr):
25
+        self._strerr = strerr
26
+
27
+    def __str__(self):
28
+        return self._strerr
29
+
30
+class PiwigoExistException(PiwigoException):
31
+    
32
+    def __init__(self, strerr):
33
+        PiwigoException.__init__(self, strerr)
34
+
35
+
36
+class Piwigo(piwigo.Piwigo):
37
+    """
38
+        describe piwigo gallery
39
+    """
40
+
41
+    def __init__(self, url):
42
+        piwigo.Piwigo.__init__(self, url)
43
+        self._login = False
44
+
45
+    def login(self, username, password):
46
+        """
47
+            login on piwigo gallery
48
+        """
49
+        self.pwg.session.login(username=username, password=password)
50
+        self._login = True
51
+        return True
52
+
53
+    def logout(self):
54
+        """
55
+            logout on piwigo gallery
56
+        """
57
+        self.pwg.session.logout()
58
+        self._login = False
59
+        return True
60
+
61
+    @property
62
+    def plan(self):
63
+        #return { (("/%s" % i["name"].replace(" / ","/")).encode('utf-8')).decode('utf-8') : i["id"] for i in self.pwg.categories.getList(recursive=True, fullname=True)['categories'] }
64
+        return { "/%s" % i["name"].replace(" / ","/") : i["id"] for i in self.pwg.categories.getList(recursive=True, fullname=True)['categories'] }
65
+
66
+
67
+    def _checkarg(fn):
68
+        def checking(self, *args, **kw):
69
+            args = list(args)
70
+            # manage path
71
+            if inspect.getargspec(fn).args.count('path'): 
72
+                pos = inspect.getargspec(fn).args.index('path') -1
73
+                if args[pos][-1] == '/' : args[pos] = args[pos][:-1]
74
+            args = tuple(args)
75
+            return fn(self, *args, **kw)
76
+        return checking    
77
+ 
78
+    def _checklogin(fn):
79
+        def checking(self, *args, **kw):
80
+            if self._login:
81
+                return fn(self, *args, **kw)
82
+            raise LoginException()
83
+        return checking    
84
+ 
85
+    @property
86
+    @_checklogin
87
+    def token(self):
88
+        """
89
+            return pwg_token
90
+        """
91
+        return self.pwg.session.getStatus()["pwg_token"]
92
+
93
+    def islogged(self):
94
+        try:
95
+            self.token
96
+        except LoginException: 
97
+            return False
98
+        return True
99
+
100
+    @_checkarg
101
+    def iscategory(self, path):
102
+        if path in self.plan:
103
+            return True
104
+        return False
105
+
106
+    @_checkarg
107
+    def idcategory(self, path):
108
+        if not self.iscategory(path):
109
+            raise PiwigoExistException("category %s not exist" % path)
110
+        return self.plan[path]
111
+   
112
+    @_checkarg
113
+    def images(self, path, **kw):
114
+        """
115
+            return list of file name image for path
116
+        """
117
+        kw["cat_id"]= self.idcategory(path)
118
+        kw["per_page"] = 200
119
+        kw["page"] = 0
120
+        imgs = {}
121
+        loop = True
122
+        while loop:
123
+            req = self.pwg.categories.getImages(**kw)
124
+            for img in req["images"]:
125
+                imgs[img["file"]] = img
126
+            if req["paging"]["count"] < req["paging"]["per_page"]:
127
+                loop = False
128
+        return imgs
129
+
130
+    @_checkarg
131
+    def sublevels(self, path, **kw):
132
+        """
133
+            return list of category in for path
134
+        """
135
+        kw["cat_id"]= self.idcategory(path)
136
+        return  { i["name"] : i for i in self.pwg.categories.getList(**kw)['categories'] if i["id"] != kw["cat_id"] }
137
+
138
+
139
+    @_checkarg
140
+    def isimage(self, path):
141
+        img = path.split('/')[-1]
142
+        path =  '/'.join(path.split('/')[:-1])
143
+        if img in self.images(path):
144
+            return True
145
+        return False
146
+    
147
+    @_checkarg
148
+    def idimage(self, path):
149
+        if not self.isimage(path):
150
+            raise PiwigoExistException("image %s not exist" % path)
151
+        img = path.split('/')[-1]
152
+        path =  '/'.join(path.split('/')[:-1])
153
+        return self.images(path)[img]["id"]
154
+
155
+    @_checkarg
156
+    @_checklogin
157
+    def mkdir(self, path, **kw):
158
+        """
159
+            create a category named path
160
+        """
161
+        kw['name'] = path.split('/')[-1]
162
+        parent = '/'.join(path.split('/')[:-1])
163
+        if parent and not self.iscategory(parent):
164
+            raise PiwigoExistException("category %s not exist" % parent)
165
+        if parent : kw['parent'] = self.plan[parent]
166
+        self.pwg.categories.add(**kw)
167
+        return self.idcategory(path)
168
+
169
+    @_checkarg
170
+    @_checklogin
171
+    def makedirs(self, path, **kw):
172
+        """
173
+            recursive category create function
174
+        """
175
+        pp = '/'
176
+        for p in path.split('/')[1:]:
177
+            pp = '%s%s' % (pp, p)
178
+            if not self.iscategory(pp):
179
+                self.mkdir(pp, **kw)
180
+            pp = '%s/' % pp
181
+        return self.idcategory(path)
182
+
183
+    @_checkarg
184
+    @_checklogin
185
+    def removedirs(self, path, **kw):
186
+        """
187
+            remove (delete) category
188
+        """
189
+        self.pwg.categories.delete(category_id=self.idcategory(path), pwg_token=self.token, **kw)
190
+        return True
191
+
192
+    @_checkarg
193
+    @_checklogin
194
+    def upload(self, image, path="", **kw):
195
+        """
196
+            upload image in path
197
+        """
198
+        kw["image"] = image
199
+        if len(path):
200
+            if not self.iscategory(path):
201
+                raise PiwigoExistException("category %s not exist" % parent)
202
+            kw['category'] = self.idcategory(path)
203
+        return self.pwg.images.addSimple(**kw)['image_id']
204
+
205
+    @_checkarg
206
+    @_checklogin
207
+    def download(self, path, dst, **kw):
208
+        """
209
+            download image dst
210
+        """
211
+        if not self.isimage(path):
212
+            raise PiwigoException("image %s not exist" % path)
213
+        img = path.split('/')[-1]
214
+        path =  '/'.join(path.split('/')[:-1])
215
+        url = self.images(path)[img]['element_url']
216
+        with open(dst, 'wb') as img:
217
+            r = requests.get(url)
218
+            img.write(r.content)
219
+            r.connection.close()
220
+        return True
221
+
222
+
223
+    @_checklogin
224
+    def remove(self, path, **kw):
225
+        """
226
+            remove (delete) image
227
+        """
228
+        if not self.isimage(path):
229
+            raise PiwigoException("image %s not exist" % path)
230
+        self.pwg.images.delete(image_id= self.idimage(path), pwg_token=self.token)
231
+        return True

+ 107
- 0
piwigotools/interface.py View File

@@ -0,0 +1,107 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import sys
4
+import os
5
+import threading
6
+try:
7
+    import Queue as queue
8
+except:
9
+    import queue
10
+import time
11
+
12
+import piwigotools.progressbar as progressbar
13
+
14
+class Step(threading.Thread):
15
+
16
+    def __init__(self, qin, qout, qerr):
17
+        threading.Thread.__init__(self)
18
+        self.qin = qin
19
+        self.qout = qout
20
+        self.qerr = qerr
21
+
22
+    def run(self):
23
+        while not self.qin.empty():
24
+            try:
25
+                call, arg, kw = self.qin.get_nowait()
26
+                try:
27
+                    call(*arg, **kw)
28
+                except Exception as e:
29
+                   print(e)
30
+                   self.qerr.put([call, arg, kw, e]) 
31
+                self.qout.put([call, arg, kw])
32
+            except queue.Empty:
33
+                pass
34
+ 
35
+class Run:
36
+
37
+    def __init__(self, name, cnt=1):
38
+        self._name = name
39
+        self._qin = queue.Queue()
40
+        self._qout = queue.Queue()
41
+        self._qerr = queue.Queue()
42
+        self._threads = [ Step(self._qin, self._qout, self._qerr) for i in range(cnt)] 
43
+
44
+    def add(self, call, arg, kw):
45
+        self._qin.put([call, arg, kw])
46
+
47
+    def start(self):
48
+        self._qout.maxsize = self._qin.qsize()
49
+        pbar = progressbar.ProgressBar(widgets=['%s ' %  self._name, 
50
+                                    progressbar.Counter() , 
51
+                                    ' on %s ' % self._qin.qsize(), 
52
+                                    progressbar.Bar(), 
53
+                                    ' ', 
54
+                                    progressbar.Timer()],
55
+                            maxval=self._qin.qsize()).start()
56
+        for thread in self._threads:
57
+            thread.start()
58
+        while not self._qout.full():
59
+            time.sleep(0.1) # sleep 0.1s
60
+            pbar.update(self._qout.qsize())
61
+        pbar.finish()    
62
+        return self._qerr
63
+
64
+class StepAnalyse(threading.Thread):
65
+
66
+    def __init__(self, pbar):
67
+        threading.Thread.__init__(self)
68
+        self._pbar = pbar
69
+        self._stopevent = threading.Event()
70
+
71
+    def run(self):
72
+        self._pbar.start()
73
+        i = 0
74
+        while not self._stopevent.isSet():
75
+            try:
76
+                self._pbar.update(i)
77
+                i = i + 1
78
+            except:
79
+                pass
80
+            time.sleep(0.1)
81
+
82
+    def stop(self):
83
+        self._stopevent.set()
84
+        self._pbar.finish()
85
+
86
+class Analyse:
87
+
88
+    def __init__(self, name):
89
+        self._name = name
90
+        pbar = progressbar.ProgressBar(widgets=['%s: ' % name, 
91
+                                            progressbar.AnimatedMarker(), 
92
+                                            ' | ',  
93
+                                            progressbar.Timer()]
94
+                                    )
95
+        self._thread = StepAnalyse(pbar)
96
+    
97
+    def start(self):
98
+        self._thread.start()
99
+
100
+    def stop(self):
101
+        self._thread.stop()
102
+
103
+def purge_kw(kw, notkw):
104
+    return {k : kw[k] for k in kw if k not in notkw}
105
+
106
+
107
+

+ 199
- 0
piwigotools/main.py View File

@@ -0,0 +1,199 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import sys
4
+import os, os.path
5
+import glob
6
+import pprint
7
+
8
+try:
9
+    from myterm.parser import OptionParser
10
+except:
11
+    from optparse import OptionParser    
12
+
13
+from piwigotools import Piwigo, __version__
14
+from piwigo.ws import Ws
15
+from piwigotools.interface import *
16
+
17
+DESCRIPTION = "tools for piwigo gallery"
18
+USAGE = """piwigo verb --param1=value1 --param2=value2
19
+verb list
20
+- upload
21
+- download
22
+- sync
23
+- ws
24
+
25
+to get help: piwigo verb --help
26
+"""
27
+AUTHOR = "Frederic Aoustin"
28
+PROG = "piwigo"
29
+VERSION = __version__
30
+
31
+VERBS = {
32
+        "upload":
33
+            { 
34
+                "usage" : "usage for verb upload",
35
+                "description" : "upload file in piwigo gallery",
36
+                "arg" : 
37
+                    {
38
+                        "category" : {"type":"string", "default":"/", "help":"destination category of piwigo gallery"},
39
+                        "source" : {"type":"string", "default":"*.jpg", "help":"path of upload picture"},
40
+                        "url" :  {"type":"string", "default":"", "help":"url of piwigo gallery"},
41
+                        "user" :  {"type":"string", "default":"", "help":"user of piwigo gallery"},
42
+                        "password" :  {"type":"string", "default":"", "help":"password of piwigo gallery"},
43
+                        "thread" :  {"type":"int", "default":"1", "help":"number of thread"},
44
+                    },
45
+            },
46
+        "download":
47
+            { 
48
+                "usage" : "usage for verb download",
49
+                "description" : "download image from piwigo gallery",
50
+                "arg" : 
51
+                    {
52
+                        "category" : {"type":"string", "default":"/", "help":"source category of piwigo gallery"},
53
+                        "dest" : {"type":"string", "default":".", "help":"path of destination"},
54
+                        "url" :  {"type":"string", "default":"", "help":"url of piwigo gallery"},
55
+                        "user" :  {"type":"string", "default":"", "help":"user of piwigo gallery"},
56
+                        "password" :  {"type":"string", "default":"", "help":"password of piwigo gallery"},
57
+                        "thread" :  {"type":"int", "default":"1", "help":"number of thread"},
58
+                    },
59
+            },
60
+        "sync":
61
+            { 
62
+                "usage" : "usage for verb sync",
63
+                "description" : "synchronization between path and piwigo gallery",
64
+                "arg" : 
65
+                    {
66
+                        "category" : {"type":"string", "default":"/", "help":"category of piwigo gallery"},
67
+                        "source" : {"type":"string", "default":".", "help":"path of picture"},
68
+                        "url" :  {"type":"string", "default":"", "help":"url of piwigo gallery"},
69
+                        "user" :  {"type":"string", "default":"", "help":"user of piwigo gallery"},
70
+                        "password" :  {"type":"string", "default":"", "help":"password of piwigo gallery"},
71
+                        "thread" :  {"type":"int", "default":"1", "help":"number of thread"},
72
+                    },
73
+            },
74
+         "ws":
75
+            { 
76
+                "usage" : "usage for verb ws",
77
+                "description" : "use web service of piwigo gallery",
78
+                "arg" : 
79
+                    {
80
+                        "method" : {"type":"string", "default":".", "help":"name of web service"},
81
+                        "url" :  {"type":"string", "default":"", "help":"url of piwigo gallery"},
82
+                    },
83
+            },
84
+ 
85
+        }
86
+
87
+def add_dynamic_option(parser):
88
+    
89
+    # add arg for verb
90
+    if not len(sys.argv) > 1:
91
+        parser.print_help()
92
+        sys.exit(1)
93
+    
94
+    if sys.argv[1] in ("--help", "-h"):
95
+        parser.print_help()
96
+        parser.print_version()
97
+        sys.exit(0)
98
+ 
99
+    if sys.argv[1] in ("--version"):
100
+        parser.print_version()
101
+        sys.exit(0)
102
+        
103
+    
104
+    verb = sys.argv[1]
105
+    arg_know = ['--help']
106
+    for arg in VERBS.get(verb, {'arg':{}})['arg']:
107
+        kw = VERBS[sys.argv[1]]['arg'][arg]
108
+        kw['dest'] = arg
109
+        parser.add_option("--%s" % arg, **kw)
110
+        arg_know.append("--%s" % arg)
111
+    # add arg in argv
112
+    for arg in sys.argv[2:]:
113
+        if arg[:2] == '--' and arg.split('=')[0] not in arg_know:
114
+            arg = arg[2:].split('=')[0]
115
+            parser.add_option("--%s" % arg , dest=arg, type="string")
116
+            arg_know.append("--%s" % arg)
117
+
118
+
119
+    #check verb
120
+    if verb not in VERBS:
121
+        parser.print_help()
122
+        parser.exit(status=2, msg='verb "%s" unknow\n' % verb)
123
+        sys.exit(0)
124
+
125
+    parser.set_usage(VERBS[verb]["usage"])
126
+    parser.description = VERBS[verb]["description"]
127
+
128
+    if '--help' in sys.argv[1:]:
129
+        parser.print_help()
130
+        sys.exit(0)
131
+
132
+
133
+
134
+def main():
135
+    usage = USAGE
136
+    parser = OptionParser(version="%s %s" % (PROG,VERSION), usage=usage)
137
+    parser.description= DESCRIPTION
138
+    parser.epilog = AUTHOR
139
+    try:
140
+        add_dynamic_option(parser)
141
+        (options, args) = parser.parse_args()
142
+        verb = args[0]
143
+        if verb == 'ws':
144
+            piwigo = Piwigo(url=options.url)
145
+            if 'user' and 'password' in options.__dict__:
146
+                piwigo.login(options.user, options.password)
147
+            kw = purge_kw(options.__dict__,('user','password','url'))
148
+            pp = pprint.PrettyPrinter(indent=4)
149
+            pp.pprint(Ws(piwigo, options.method)(**kw))
150
+            if piwigo.islogged:
151
+                piwigo.logout()
152
+        if verb == "download":
153
+            ana = Analyse('Analyze')
154
+            ana.start()
155
+            piwigo = Piwigo(url=options.url)
156
+            piwigo.login(options.user, options.password)
157
+            # check
158
+            if not os.path.isdir(options.dest):
159
+                os.makedirs(options.dest)        
160
+            options.dest = os.path.abspath(options.dest)
161
+            piwigo.iscategory(options.category)
162
+            if options.category[-1] == '/' : options.category = options.category[:-1]
163
+            # treatment
164
+            run = Run(verb, options.thread)
165
+            kw = purge_kw(options.__dict__,('user','password','url','dest','category','thread'))
166
+            for img in piwigo.images(options.category, **kw):
167
+                run.add(piwigo.download, 
168
+                        ["%s/%s" % (options.category, str(img)), "%s/%s" % (options.dest, str(img))],
169
+                        kw)
170
+            ana.stop()
171
+            run.start()
172
+            piwigo.logout()
173
+        if verb == "upload":
174
+            ana = Analyse('Analyze')
175
+            ana.start()
176
+            piwigo = Piwigo(url=options.url)
177
+            piwigo.login(options.user, options.password)
178
+            # check
179
+            piwigo.makedirs(options.category)
180
+            # treatment
181
+            run = Run(verb, options.thread)
182
+            kw = purge_kw(options.__dict__,('user','password','url','source','category','thread'))
183
+            for img in glob.glob(options.source):
184
+                run.add(piwigo.upload,
185
+                        [os.path.abspath(img), options.category], 
186
+                        kw)
187
+            ana.stop()
188
+            run.start()
189
+            piwigo.logout()
190
+    except Exception as e:
191
+        parser.error(e)
192
+        sys.exit(1)
193
+
194
+if __name__ == "__main__":
195
+    main()
196
+
197
+# TODO
198
+# verb sync
199
+# test python3: problem request return bytes and not str ... only str python2 or 3 and encoding?

+ 54
- 0
piwigotools/progressbar/__init__.py View File

@@ -0,0 +1,54 @@
1
+#!/usr/bin/python
2
+# -*- coding: utf-8 -*-
3
+#
4
+# progressbar  - Text progress bar library for Python.
5
+# Copyright (c) 2005 Nilton Volpato
6
+#
7
+# This library is free software; you can redistribute it and/or
8
+# modify it under the terms of the GNU Lesser General Public
9
+# License as published by the Free Software Foundation; either
10
+# version 2.1 of the License, or (at your option) any later version.
11
+#
12
+# This library is distributed in the hope that it will be useful,
13
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
+# Lesser General Public License for more details.
16
+#
17
+# You should have received a copy of the GNU Lesser General Public
18
+# License along with this library; if not, write to the Free Software
19
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
+
21
+
22
+# IMPORTANT
23
+# copy of https://github.com/coagulant/progressbar-python3
24
+
25
+
26
+"""Text progress bar library for Python.
27
+
28
+A text progress bar is typically used to display the progress of a long
29
+running operation, providing a visual cue that processing is underway.
30
+
31
+The ProgressBar class manages the current progress, and the format of the line
32
+is given by a number of widgets. A widget is an object that may display
33
+differently depending on the state of the progress bar. There are three types
34
+of widgets:
35
+ - a string, which always shows itself
36
+
37
+ - a ProgressBarWidget, which may return a different value every time its
38
+   update method is called
39
+
40
+ - a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it
41
+   expands to fill the remaining width of the line.
42
+
43
+The progressbar module is very easy to use, yet very powerful. It will also
44
+automatically enable features like auto-resizing when the system supports it.
45
+"""
46
+
47
+__author__ = 'Nilton Volpato'
48
+__author_email__ = 'first-name dot last-name @ gmail.com'
49
+__date__ = '2011-05-14'
50
+__version__ = '2.3dev'
51
+
52
+from .compat import *
53
+from .widgets import *
54
+from .progressbar import *

+ 45
- 0
piwigotools/progressbar/compat.py View File

@@ -0,0 +1,45 @@
1
+#!/usr/bin/python
2
+# -*- coding: utf-8 -*-
3
+#
4
+# progressbar  - Text progress bar library for Python.
5
+# Copyright (c) 2005 Nilton Volpato
6
+#
7
+# This library is free software; you can redistribute it and/or
8
+# modify it under the terms of the GNU Lesser General Public
9
+# License as published by the Free Software Foundation; either
10
+# version 2.1 of the License, or (at your option) any later version.
11
+#
12
+# This library is distributed in the hope that it will be useful,
13
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
+# Lesser General Public License for more details.
16
+#
17
+# You should have received a copy of the GNU Lesser General Public
18
+# License along with this library; if not, write to the Free Software
19
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
+
21
+"""Compatibility methods and classes for the progressbar module."""
22
+
23
+
24
+# Python 3.x (and backports) use a modified iterator syntax
25
+# This will allow 2.x to behave with 3.x iterators
26
+try:
27
+  next
28
+except NameError:
29
+    def next(iter):
30
+        try:
31
+            # Try new style iterators
32
+            return iter.__next__()
33
+        except AttributeError:
34
+            # Fallback in case of a "native" iterator
35
+            return iter.next()
36
+
37
+
38
+# Python < 2.5 does not have "any"
39
+try:
40
+  any
41
+except NameError:
42
+   def any(iterator):
43
+      for item in iterator:
44
+         if item: return True
45
+      return False

+ 296
- 0
piwigotools/progressbar/progressbar.py View File

@@ -0,0 +1,296 @@
1
+#!/usr/bin/python
2
+# -*- coding: utf-8 -*-
3
+#
4
+# progressbar  - Text progress bar library for Python.
5
+# Copyright (c) 2005 Nilton Volpato
6
+#
7
+# This library is free software; you can redistribute it and/or
8
+# modify it under the terms of the GNU Lesser General Public
9
+# License as published by the Free Software Foundation; either
10
+# version 2.1 of the License, or (at your option) any later version.
11
+#
12
+# This library is distributed in the hope that it will be useful,
13
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
+# Lesser General Public License for more details.
16
+#
17
+# You should have received a copy of the GNU Lesser General Public
18
+# License along with this library; if not, write to the Free Software
19
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
+
21
+"""Main ProgressBar class."""
22
+
23
+from __future__ import division
24
+
25
+import math
26
+import os
27
+import signal
28
+import sys
29
+import time
30
+
31
+try:
32
+    from fcntl import ioctl
33
+    from array import array
34
+    import termios
35
+except ImportError:
36
+    pass
37
+
38
+from . import widgets
39
+
40
+
41
+class UnknownLength: pass
42
+
43
+
44
+class ProgressBar(object):
45
+    """The ProgressBar class which updates and prints the bar.
46
+
47
+    A common way of using it is like:
48
+    >>> pbar = ProgressBar().start()
49
+    >>> for i in range(100):
50
+    ...    # do something
51
+    ...    pbar.update(i+1)
52
+    ...
53
+    >>> pbar.finish()
54
+
55
+    You can also use a ProgressBar as an iterator:
56
+    >>> progress = ProgressBar()
57
+    >>> for i in progress(some_iterable):
58
+    ...    # do something
59
+    ...
60
+
61
+    Since the progress bar is incredibly customizable you can specify
62
+    different widgets of any type in any order. You can even write your own
63
+    widgets! However, since there are already a good number of widgets you
64
+    should probably play around with them before moving on to create your own
65
+    widgets.
66
+
67
+    The term_width parameter represents the current terminal width. If the
68
+    parameter is set to an integer then the progress bar will use that,
69
+    otherwise it will attempt to determine the terminal width falling back to
70
+    80 columns if the width cannot be determined.
71
+
72
+    When implementing a widget's update method you are passed a reference to
73
+    the current progress bar. As a result, you have access to the
74
+    ProgressBar's methods and attributes. Although there is nothing preventing
75
+    you from changing the ProgressBar you should treat it as read only.
76
+
77
+    Useful methods and attributes include (Public API):
78
+     - currval: current progress (0 <= currval <= maxval)
79
+     - maxval: maximum (and final) value
80
+     - finished: True if the bar has finished (reached 100%)
81
+     - start_time: the time when start() method of ProgressBar was called
82
+     - seconds_elapsed: seconds elapsed since start_time and last call to
83
+                        update
84
+     - percentage(): progress in percent [0..100]
85
+    """
86
+
87
+    __slots__ = ('currval', 'fd', 'finished', 'last_update_time',
88
+                 'left_justify', 'maxval', 'next_update', 'num_intervals',
89
+                 'poll', 'seconds_elapsed', 'signal_set', 'start_time',
90
+                 'term_width', 'update_interval', 'widgets', '_time_sensitive',
91
+                 '__iterable')
92
+
93
+    _DEFAULT_MAXVAL = 100
94
+    _DEFAULT_TERMSIZE = 80
95
+    _DEFAULT_WIDGETS = [widgets.Percentage(), ' ', widgets.Bar()]
96
+
97
+    def __init__(self, maxval=None, widgets=None, term_width=None, poll=1,
98
+                 left_justify=True, fd=sys.stderr):
99
+        """Initializes a progress bar with sane defaults."""
100
+
101
+        # Don't share a reference with any other progress bars
102
+        if widgets is None:
103
+            widgets = list(self._DEFAULT_WIDGETS)
104
+
105
+        self.maxval = maxval
106
+        self.widgets = widgets
107
+        self.fd = fd
108
+        self.left_justify = left_justify
109
+
110
+        self.signal_set = False
111
+        if term_width is not None:
112
+            self.term_width = term_width
113
+        else:
114
+            try:
115
+                self._handle_resize()
116
+                signal.signal(signal.SIGWINCH, self._handle_resize)
117
+                self.signal_set = True
118
+            except (SystemExit, KeyboardInterrupt): raise
119
+            except:
120
+                self.term_width = self._env_size()
121
+
122
+        self.__iterable = None
123
+        self._update_widgets()
124
+        self.currval = 0
125
+        self.finished = False
126
+        self.last_update_time = None
127
+        self.poll = poll
128
+        self.seconds_elapsed = 0
129
+        self.start_time = None
130
+        self.update_interval = 1
131
+
132
+
133
+    def __call__(self, iterable):
134
+        """Use a ProgressBar to iterate through an iterable."""
135
+
136
+        try:
137
+            self.maxval = len(iterable)
138
+        except:
139
+            if self.maxval is None:
140
+                self.maxval = UnknownLength
141
+
142
+        self.__iterable = iter(iterable)
143
+        return self
144
+
145
+
146
+    def __iter__(self):
147
+        return self
148
+
149
+
150
+    def __next__(self):
151
+        try:
152
+            value = next(self.__iterable)
153
+            if self.start_time is None: self.start()
154
+            else: self.update(self.currval + 1)
155
+            return value
156
+        except StopIteration:
157
+            self.finish()
158
+            raise
159
+
160
+
161
+    # Create an alias so that Python 2.x won't complain about not being
162
+    # an iterator.
163
+    next = __next__
164
+
165
+
166
+    def _env_size(self):
167
+        """Tries to find the term_width from the environment."""
168
+
169
+        return int(os.environ.get('COLUMNS', self._DEFAULT_TERMSIZE)) - 1
170
+
171
+
172
+    def _handle_resize(self, signum=None, frame=None):
173
+        """Tries to catch resize signals sent from the terminal."""
174
+
175
+        h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2]
176
+        self.term_width = w
177
+
178
+
179
+    def percentage(self):
180
+        """Returns the progress as a percentage."""
181
+        return self.currval * 100.0 / self.maxval
182
+
183
+    percent = property(percentage)
184
+
185
+
186
+    def _format_widgets(self):
187
+        result = []
188
+        expanding = []
189
+        width = self.term_width
190
+
191
+        for index, widget in enumerate(self.widgets):
192
+            if isinstance(widget, widgets.WidgetHFill):
193
+                result.append(widget)
194
+                expanding.insert(0, index)
195
+            else:
196
+                widget = widgets.format_updatable(widget, self)
197
+                result.append(widget)
198
+                width -= len(widget)
199
+
200
+        count = len(expanding)
201
+        while count:
202
+            portion = max(int(math.ceil(width * 1. / count)), 0)
203
+            index = expanding.pop()
204
+            count -= 1
205
+
206
+            widget = result[index].update(self, portion)
207
+            width -= len(widget)
208
+            result[index] = widget
209
+
210
+        return result
211
+
212
+
213
+    def _format_line(self):
214
+        """Joins the widgets and justifies the line."""
215
+
216
+        widgets = ''.join(self._format_widgets())
217
+
218
+        if self.left_justify: return widgets.ljust(self.term_width)
219
+        else: return widgets.rjust(self.term_width)
220
+
221
+
222
+    def _need_update(self):
223
+        """Returns whether the ProgressBar should redraw the line."""
224
+        if self.currval >= self.next_update or self.finished: return True
225
+
226
+        delta = time.time() - self.last_update_time
227
+        return self._time_sensitive and delta > self.poll
228
+
229
+
230
+    def _update_widgets(self):
231
+        """Checks all widgets for the time sensitive bit."""
232
+
233
+        self._time_sensitive = any(getattr(w, 'TIME_SENSITIVE', False)
234
+                                    for w in self.widgets)
235
+
236
+
237
+    def update(self, value=None):
238
+        """Updates the ProgressBar to a new value."""
239
+
240
+        if value is not None and value is not UnknownLength:
241
+            if (self.maxval is not UnknownLength
242
+                and not 0 <= value <= self.maxval):
243
+
244
+                raise ValueError('Value out of range')
245
+
246
+            self.currval = value
247
+
248
+
249
+        if not self._need_update(): return
250
+        if self.start_time is None:
251
+            raise RuntimeError('You must call "start" before calling "update"')
252
+
253
+        now = time.time()
254
+        self.seconds_elapsed = now - self.start_time
255
+        self.next_update = self.currval + self.update_interval
256
+        self.fd.write(self._format_line() + '\r')
257
+        self.last_update_time = now
258
+
259
+
260
+    def start(self):
261
+        """Starts measuring time, and prints the bar at 0%.
262
+
263
+        It returns self so you can use it like this:
264
+        >>> pbar = ProgressBar().start()
265
+        >>> for i in range(100):
266
+        ...    # do something
267
+        ...    pbar.update(i+1)
268
+        ...
269
+        >>> pbar.finish()
270
+        """
271
+
272
+        if self.maxval is None:
273
+            self.maxval = self._DEFAULT_MAXVAL
274
+
275
+        self.num_intervals = max(100, self.term_width)
276
+        self.next_update = 0
277
+
278
+        if self.maxval is not UnknownLength:
279
+            if self.maxval < 0: raise ValueError('Value out of range')
280
+            self.update_interval = self.maxval / self.num_intervals
281
+
282
+
283
+        self.start_time = self.last_update_time = time.time()
284
+        self.update(0)
285
+
286
+        return self
287
+
288
+
289
+    def finish(self):
290
+        """Puts the ProgressBar bar in the finished state."""
291
+
292
+        self.finished = True
293
+        self.update(self.maxval)
294
+        self.fd.write('\n')
295
+        if self.signal_set:
296
+            signal.signal(signal.SIGWINCH, signal.SIG_DFL)

+ 311
- 0
piwigotools/progressbar/widgets.py View File

@@ -0,0 +1,311 @@
1
+#!/usr/bin/python
2
+# -*- coding: utf-8 -*-
3
+#
4
+# progressbar  - Text progress bar library for Python.
5
+# Copyright (c) 2005 Nilton Volpato
6
+#
7
+# This library is free software; you can redistribute it and/or
8
+# modify it under the terms of the GNU Lesser General Public
9
+# License as published by the Free Software Foundation; either
10
+# version 2.1 of the License, or (at your option) any later version.
11
+#
12
+# This library is distributed in the hope that it will be useful,
13
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
+# Lesser General Public License for more details.
16
+#
17
+# You should have received a copy of the GNU Lesser General Public
18
+# License along with this library; if not, write to the Free Software
19
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
+
21
+"""Default ProgressBar widgets."""
22
+
23
+from __future__ import division
24
+
25
+import datetime
26
+import math
27
+
28
+try:
29
+    from abc import ABCMeta, abstractmethod
30
+except ImportError:
31
+    AbstractWidget = object
32
+    abstractmethod = lambda fn: fn
33
+else:
34
+    AbstractWidget = ABCMeta('AbstractWidget', (object,), {})
35
+
36
+
37
+def format_updatable(updatable, pbar):
38
+    if hasattr(updatable, 'update'): return updatable.update(pbar)
39
+    else: return updatable
40
+
41
+
42
+class Widget(AbstractWidget):
43
+    """The base class for all widgets.
44
+
45
+    The ProgressBar will call the widget's update value when the widget should
46
+    be updated. The widget's size may change between calls, but the widget may
47
+    display incorrectly if the size changes drastically and repeatedly.
48
+
49
+    The boolean TIME_SENSITIVE informs the ProgressBar that it should be
50
+    updated more often because it is time sensitive.
51
+    """
52
+
53
+    TIME_SENSITIVE = False
54
+    __slots__ = ()
55
+
56
+    @abstractmethod
57
+    def update(self, pbar):
58
+        """Updates the widget.
59
+
60
+        pbar - a reference to the calling ProgressBar
61
+        """
62
+
63
+
64
+class WidgetHFill(Widget):
65
+    """The base class for all variable width widgets.
66
+
67
+    This widget is much like the \\hfill command in TeX, it will expand to
68
+    fill the line. You can use more than one in the same line, and they will
69
+    all have the same width, and together will fill the line.
70
+    """
71
+
72
+    @abstractmethod
73
+    def update(self, pbar, width):
74
+        """Updates the widget providing the total width the widget must fill.
75
+
76
+        pbar - a reference to the calling ProgressBar
77
+        width - The total width the widget must fill
78
+        """
79
+
80
+
81
+class Timer(Widget):
82
+    """Widget which displays the elapsed seconds."""
83
+
84
+    __slots__ = ('format_string',)
85
+    TIME_SENSITIVE = True
86
+
87
+    def __init__(self, format='Elapsed Time: %s'):
88
+        self.format_string = format
89
+
90
+    @staticmethod
91
+    def format_time(seconds):
92
+        """Formats time as the string "HH:MM:SS"."""
93
+
94
+        return str(datetime.timedelta(seconds=int(seconds)))
95
+
96
+
97
+    def update(self, pbar):
98
+        """Updates the widget to show the elapsed time."""
99
+
100
+        return self.format_string % self.format_time(pbar.seconds_elapsed)
101
+
102
+
103
+class ETA(Timer):
104
+    """Widget which attempts to estimate the time of arrival."""
105
+
106
+    TIME_SENSITIVE = True
107
+
108
+    def update(self, pbar):
109
+        """Updates the widget to show the ETA or total time when finished."""
110
+
111
+        if pbar.currval == 0:
112
+            return 'ETA:  --:--:--'
113
+        elif pbar.finished:
114
+            return 'Time: %s' % self.format_time(pbar.seconds_elapsed)
115
+        else:
116
+            elapsed = pbar.seconds_elapsed
117
+            eta = elapsed * pbar.maxval / pbar.currval - elapsed
118
+            return 'ETA:  %s' % self.format_time(eta)
119
+
120
+
121
+class FileTransferSpeed(Widget):
122
+    """Widget for showing the transfer speed (useful for file transfers)."""
123
+
124
+    FORMAT = '%6.2f %s%s/s'
125
+    PREFIXES = ' kMGTPEZY'
126
+    __slots__ = ('unit',)
127
+
128
+    def __init__(self, unit='B'):
129
+        self.unit = unit
130
+
131
+    def update(self, pbar):
132
+        """Updates the widget with the current SI prefixed speed."""
133
+
134
+        if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6: # =~ 0
135
+            scaled = power = 0
136
+        else:
137
+            speed = pbar.currval / pbar.seconds_elapsed
138
+            power = int(math.log(speed, 1000))
139
+            scaled = speed / 1000.**power
140
+
141
+        return self.FORMAT % (scaled, self.PREFIXES[power], self.unit)
142
+
143
+
144
+class AnimatedMarker(Widget):
145
+    """An animated marker for the progress bar which defaults to appear as if
146
+    it were rotating.
147
+    """
148
+
149
+    __slots__ = ('markers', 'curmark')
150
+
151
+    def __init__(self, markers='|/-\\'):
152
+        self.markers = markers
153
+        self.curmark = -1
154
+
155
+    def update(self, pbar):
156
+        """Updates the widget to show the next marker or the first marker when
157
+        finished"""
158
+
159
+        if pbar.finished: return self.markers[0]
160
+
161
+        self.curmark = (self.curmark + 1) % len(self.markers)
162
+        return self.markers[self.curmark]
163
+
164
+# Alias for backwards compatibility
165
+RotatingMarker = AnimatedMarker
166
+
167
+
168
+class Counter(Widget):
169
+    """Displays the current count."""
170
+
171
+    __slots__ = ('format_string',)
172
+
173
+    def __init__(self, format='%d'):
174
+        self.format_string = format
175
+
176
+    def update(self, pbar):
177
+        return self.format_string % pbar.currval
178
+
179
+
180
+class Percentage(Widget):
181
+    """Displays the current percentage as a number with a percent sign."""
182
+
183
+    def update(self, pbar):
184
+        return '%3d%%' % pbar.percentage()
185
+
186
+
187
+class FormatLabel(Timer):
188
+    """Displays a formatted label."""
189
+
190
+    mapping = {
191
+        'elapsed': ('seconds_elapsed', Timer.format_time),
192
+        'finished': ('finished', None),
193
+        'last_update': ('last_update_time', None),
194
+        'max': ('maxval', None),
195
+        'seconds': ('seconds_elapsed', None),
196
+        'start': ('start_time', None),
197
+        'value': ('currval', None)
198
+    }
199
+
200
+    __slots__ = ('format_string',)
201
+    def __init__(self, format):
202
+        self.format_string = format
203
+
204
+    def update(self, pbar):
205
+        context = {}
206
+        for name, (key, transform) in self.mapping.items():
207
+            try:
208
+                value = getattr(pbar, key)
209
+
210
+                if transform is None:
211
+                   context[name] = value
212
+                else:
213
+                   context[name] = transform(value)
214
+            except: pass
215
+
216
+        return self.format_string % context
217
+
218
+
219
+class SimpleProgress(Widget):
220
+    """Returns progress as a count of the total (e.g.: "5 of 47")."""
221
+
222
+    __slots__ = ('sep',)
223
+
224
+    def __init__(self, sep=' of '):
225
+        self.sep = sep
226
+
227
+    def update(self, pbar):
228
+        return '%d%s%d' % (pbar.currval, self.sep, pbar.maxval)
229
+
230
+
231
+class Bar(WidgetHFill):
232
+    """A progress bar which stretches to fill the line."""
233
+
234
+    __slots__ = ('marker', 'left', 'right', 'fill', 'fill_left')
235
+
236
+    def __init__(self, marker='#', left='|', right='|', fill=' ',
237
+                 fill_left=True):
238
+        """Creates a customizable progress bar.
239
+
240
+        marker - string or updatable object to use as a marker
241
+        left - string or updatable object to use as a left border
242
+        right - string or updatable object to use as a right border
243
+        fill - character to use for the empty part of the progress bar
244
+        fill_left - whether to fill from the left or the right
245
+        """
246
+        self.marker = marker
247
+        self.left = left
248
+        self.right = right
249
+        self.fill = fill
250
+        self.fill_left = fill_left
251
+
252
+
253
+    def update(self, pbar, width):
254
+        """Updates the progress bar and its subcomponents."""
255
+
256
+        left, marked, right = (format_updatable(i, pbar) for i in
257
+                               (self.left, self.marker, self.right))
258
+
259
+        width -= len(left) + len(right)
260
+        # Marked must *always* have length of 1
261
+        if pbar.maxval:
262
+          marked *= int(pbar.currval / pbar.maxval * width)
263
+        else:
264
+          marked = ''
265
+
266
+        if self.fill_left:
267
+            return '%s%s%s' % (left, marked.ljust(width, self.fill), right)
268
+        else:
269
+            return '%s%s%s' % (left, marked.rjust(width, self.fill), right)
270
+
271
+
272
+class ReverseBar(Bar):
273
+    """A bar which has a marker which bounces from side to side."""
274
+
275
+    def __init__(self, marker='#', left='|', right='|', fill=' ',
276
+                 fill_left=False):
277
+        """Creates a customizable progress bar.
278
+
279
+        marker - string or updatable object to use as a marker
280
+        left - string or updatable object to use as a left border
281
+        right - string or updatable object to use as a right border
282
+        fill - character to use for the empty part of the progress bar
283
+        fill_left - whether to fill from the left or the right
284
+        """
285
+        self.marker = marker
286
+        self.left = left
287
+        self.right = right
288
+        self.fill = fill
289
+        self.fill_left = fill_left
290
+
291
+
292
+class BouncingBar(Bar):
293
+    def update(self, pbar, width):
294
+        """Updates the progress bar and its subcomponents."""
295
+
296
+        left, marker, right = (format_updatable(i, pbar) for i in
297
+                               (self.left, self.marker, self.right))
298
+
299
+        width -= len(left) + len(right)
300
+
301
+        if pbar.finished: return '%s%s%s' % (left, width * marker, right)
302
+
303
+        position = int(pbar.currval % (width * 2 - 1))
304
+        if position > width: position = width * 2 - position
305
+        lpad = self.fill * (position - 1)
306
+        rpad = self.fill * (width - len(marker) - len(lpad))
307
+
308
+        # Swap if we want to bounce the other way
309
+        if not self.fill_left: rpad, lpad = lpad, rpad
310
+
311
+        return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)

+ 8
- 2
setup.py View File

@@ -11,8 +11,9 @@ import piwigotools
11 11
 
12 12
 NAME = "piwigotools"
13 13
 VERSION = piwigotools.__version__
14
-DESC = "piwigotools description"
15
-URLPKG = "https://url/of/piwigotools/website"
14
+DESC = "mange your piwigo gallery by command piwigo"
15
+URLPKG = "https://github.com/fraoustin/piwigotools"
16
+
16 17
 
17 18
 HERE = os.path.abspath(os.path.dirname(__file__))
18 19
 
@@ -44,4 +45,9 @@ setup(
44 45
     install_requires=REQUIRED,
45 46
     url=URLPKG,
46 47
     classifiers=CLASSIFIED,
48
+    entry_points = {
49
+        'console_scripts': [
50
+            'piwigo = piwigotools.main:main',
51
+        ],
52
+    },
47 53
 )

BIN
tests/samplepiwigotools.jpg View File


+ 82
- 1
tests/test_basic.py View File

@@ -6,13 +6,94 @@
6 6
 """
7 7
 
8 8
 import unittest
9
+import os
10
+import os.path
11
+
12
+from piwigotools import Piwigo, LoginException, PiwigoExistException
9 13
 
10 14
 class BasicTestCase(unittest.TestCase):
11 15
     """
12 16
         Class for Basic Test for piwigotools
13 17
     """
14 18
     def setUp(self):
15
-        pass
19
+        self.url = "http://mygallery.piwigo.com/"
20
+        self.usertest = 'USERTEST'
21
+        self.passwordtest = 'xxxxxx'
22
+        self.piwigo = Piwigo(self.url)
23
+
24
+    def test_basic(self):
25
+        self.assertTrue(self.piwigo.pwg.getVersion())
26
+    
27
+    def test_checkLogin(self):
28
+        self.assertTrue(self.piwigo.login(self.usertest, self.passwordtest))
29
+        self.assertTrue(self.piwigo.logout())
30
+        self.assertRaises(LoginException, self.piwigo.mkdir)
31
+        self.assertRaises(LoginException, self.piwigo.makedirs)
32
+        self.assertRaises(LoginException, self.piwigo.upload)
33
+
34
+    def test_createCategory(self):
35
+        self.piwigo.login(self.usertest, self.passwordtest)
36
+        self.assertTrue(self.piwigo.mkdir('/level'))
37
+        self.assertTrue(self.piwigo.mkdir('/level/sublevel'))
38
+        self.assertTrue(self.piwigo.makedirs('/level2/sublevel2'))
39
+        self.piwigo.removedirs('/level2')
40
+        self.piwigo.removedirs('/level')
41
+        self.piwigo.logout()
42
+
43
+    def test_checkpath(self):
44
+        self.piwigo.login(self.usertest, self.passwordtest)
45
+        self.piwigo.mkdir('/level')
46
+        self.assertTrue(self.piwigo.iscategory('/level'))
47
+        self.assertTrue(self.piwigo.iscategory('/level/'))
48
+        self.piwigo.removedirs('/level')
49
+        self.piwigo.logout()
50
+
51
+    def test_removeCategory(self):
52
+        self.piwigo.login(self.usertest, self.passwordtest)
53
+        self.piwigo.makedirs('/level2/sublevel2')
54
+        self.assertTrue(self.piwigo.removedirs('/level2'))
55
+        self.assertFalse(self.piwigo.iscategory('/level2'))
56
+        self.piwigo.logout()
57
+
58
+    def test_uploadImage(self):
59
+        self.piwigo.login(self.usertest, self.passwordtest)
60
+        self.piwigo.mkdir('/level')
61
+        img = os.path.join(os.path.dirname(os.path.abspath(__file__)),'samplepiwigotools.jpg')
62
+        id = self.piwigo.upload(image=img, path="/level")
63
+        self.assertTrue(id)
64
+        self.assertTrue(self.piwigo.isimage('/level/samplepiwigotools.jpg'))
65
+        self.piwigo.pwg.images.delete(image_id=id, pwg_token=self.piwigo.token)
66
+        self.piwigo.removedirs('/level')
67
+        self.piwigo.logout()
68
+
69
+    def test_removeImage(self):
70
+        self.piwigo.login(self.usertest, self.passwordtest)
71
+        self.piwigo.mkdir('/level')
72
+        img = os.path.join(os.path.dirname(os.path.abspath(__file__)),'samplepiwigotools.jpg')
73
+        id = self.piwigo.upload(image=img, path="/level")
74
+        self.assertTrue(self.piwigo.remove('/level/samplepiwigotools.jpg'))
75
+        self.assertFalse(self.piwigo.isimage('/level/samplepiwigotools.jpg'))
76
+        self.piwigo.removedirs('/level')
77
+        self.piwigo.logout()
78
+
79
+    def test_sublevel(self):
80
+        self.piwigo.login(self.usertest, self.passwordtest)
81
+        self.piwigo.makedirs('/level2/sublevel2')
82
+        self.assertTrue(len(self.piwigo.sublevels('/level2')))
83
+        self.piwigo.removedirs('/level2')
84
+        self.piwigo.logout()
85
+
86
+    def test_downloadImage(self):
87
+        self.piwigo.login(self.usertest, self.passwordtest)
88
+        self.piwigo.mkdir('/level')
89
+        img = os.path.join(os.path.dirname(os.path.abspath(__file__)),'samplepiwigotools.jpg')
90
+        id = self.piwigo.upload(image=img, path="/level")
91
+        imgdst = os.path.join(os.path.dirname(os.path.abspath(__file__)),'download.jpg')
92
+        self.assertTrue(self.piwigo.download("/level/samplepiwigotools.jpg",imgdst))
93
+        os.remove(imgdst)
94
+        self.piwigo.remove('/level/samplepiwigotools.jpg')
95
+        self.piwigo.removedirs('/level')
96
+        self.piwigo.logout()
16 97
 
17 98
 if __name__ == '__main__':
18 99
     unittest.main()