├── LICENSE ├── pyacd ├── status.py ├── types.py ├── exception.py ├── multipart.py ├── __init__.py ├── auth.py ├── connection.py ├── apiresponse.py └── api.py ├── setup.py ├── README ├── bin ├── acdcat ├── acdmkdir ├── acdrecycle ├── acdget ├── acdlist └── acdput └── test.py /LICENSE: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for *YOUNGER* than you, not *OLDER*. 23 | # 24 | -------------------------------------------------------------------------------- /pyacd/status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for Younger than you, not Older. 23 | # 24 | 25 | """Entity statuses in Amazon Cloud Drive 26 | 27 | AVIABLE means normal status 28 | PENDING means that file was created but have not been finished. 29 | """ 30 | 31 | AVIABLE="AVIABLE" 32 | PENDING="PENDING" 33 | -------------------------------------------------------------------------------- /pyacd/types.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for Younger than you, not Older. 23 | # 24 | 25 | """Entity types in Amazon Cloud Drive 26 | 27 | ROOT means "/". 28 | RECYCLE means "/RecycleBin". 29 | FOLDER means folder. 30 | FILE means file. 31 | """ 32 | 33 | ROOT="ROOT" 34 | RECYCLE="RECYCLE" 35 | FOLDER="FOLDER" 36 | FILE="FILE" 37 | 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2012 Matt Luongo http://mattluongo.com 4 | # All rights reserved. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a 7 | # copy of this software and associated documentation files (the 8 | # "Software"), to deal in the Software without restriction, including 9 | # without limitation the rights to use, copy, modify, merge, publish, dis- 10 | # tribute, sublicense, and/or sell copies of the Software, and to permit 11 | # persons to whom the Software is furnished to do so, subject to the fol- 12 | # lowing conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included 15 | # in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 19 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 20 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | 25 | from setuptools import setup 26 | 27 | setup(name = "amazon-cloud-drive", 28 | version = '0.0.6', 29 | description = "A maintained fork of PyAmazonCloudDrive (pyacd), a "\ 30 | "3rd-party Python library for accessing Amazon Cloud "\ 31 | "Drives.", 32 | author = "Sakurai Youhei", 33 | maintainer = "Matt Luongo", 34 | maintainer_email = "mhluongo@gmail.com", 35 | scripts = ["bin/acdcat", "bin/acdget", "bin/acdlist", "bin/acdmkdir", 36 | "bin/acdput", "bin/acdrecycle"], 37 | url = "https://github.com/mhluongo/amazon-cloud-drive", 38 | packages = ["pyacd"], 39 | license = "MIT", 40 | classifiers = ["Development Status :: 4 - Beta", 41 | "Intended Audience :: Developers", 42 | "License :: OSI Approved :: MIT License", 43 | "Operating System :: POSIX"] 44 | ) 45 | -------------------------------------------------------------------------------- /pyacd/exception.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for Younger than you, not Older. 23 | # 24 | 25 | 26 | class PyAmazonCloudDriveError(StandardError): 27 | """General error""" 28 | 29 | def __init__(self, reason): 30 | StandardError.__init__(self) 31 | self.reason = reason 32 | 33 | def __repr__(self): 34 | return 'PyAmazonCloudDriveError: %s' % self.reason 35 | 36 | def __str__(self): 37 | return 'PyAmazonCloudDriveError: %s' % self.reason 38 | 39 | class PyAmazonCloudDriveApiException(PyAmazonCloudDriveError): 40 | """server returns error code and message""" 41 | 42 | def __init__(self,error_obj): 43 | if not isinstance(error_obj,dict): 44 | PyAmazonCloudDriveError.__init__(self,error_obj) 45 | else: 46 | self.message=error_obj.get("Message") 47 | self.code=error_obj.get("Code") 48 | self._type=error_obj.get("Type") 49 | PyAmazonCloudDriveError.__init__(self,"%s:%s"%(self.code,self.message)) 50 | -------------------------------------------------------------------------------- /pyacd/multipart.py: -------------------------------------------------------------------------------- 1 | # Original code is following URL 2 | # http://stackoverflow.com/questions/1254270/multipart-form-post-to-google-app-engine-not-working 3 | 4 | 5 | import mimetypes 6 | import urllib2 7 | import httplib 8 | import sys 9 | 10 | import pyacd 11 | from pyacd.exception import PyAmazonCloudDriveError 12 | from pyacd.connection import gen_httplib_conn 13 | 14 | def post_multipart(url, fields, files): 15 | method="POST" 16 | content_type, body = encode_multipart_formdata(fields, files) 17 | #print body 18 | 19 | # Issue 1 20 | # http://code.google.com/p/pyamazonclouddrive/issues/detail?id=1 21 | hs={'content-type': content_type,'content-length': str(len(body))} 22 | 23 | scheme,host = urllib2.urlparse.urlparse(url)[:2] 24 | 25 | conn=gen_httplib_conn(scheme,host) 26 | 27 | path = url.split(host,1)[1] 28 | conn.request(method,path,body,hs) 29 | 30 | if pyacd.debug_level: 31 | sys.stderr.write(method) 32 | 33 | resp = conn.getresponse() 34 | #print "code->",resp.status 35 | if 400< resp.status <599: 36 | sys.stderr.write(resp.read()) 37 | raise PyAmazonCloudDriveError("response code is %d"%resp.status) 38 | 39 | if pyacd.debug_level: 40 | sys.stderr.write("->") 41 | 42 | #if resp.getheader("Location"): 43 | # return pyacd.conn.do_get(resp.getheader("Location")) 44 | 45 | resp_body=resp.read() 46 | conn.close() 47 | return resp_body 48 | 49 | def encode_multipart_formdata(fields, files): 50 | """ 51 | fields is a dict of (name, value) elements for regular form fields. 52 | files is a dict of (filename, filedata) elements for data to be uploaded as files 53 | Return (content_type, body) ready for httplib.HTTP instance 54 | """ 55 | BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' 56 | CRLF = '\r\n' 57 | L = [] 58 | for key, value in fields.items(): 59 | L.append('--' + BOUNDARY) 60 | L.append('Content-Disposition: form-data; name="%s"' % key) 61 | L.append('') 62 | L.append(value) 63 | for filename, filedata in files.items(): 64 | L.append('--' + BOUNDARY) 65 | L.append('Content-Disposition: form-data; name="file"; filename="%s"' % (filename)) 66 | L.append('Content-Type: %s' % 'application/octet-stream') 67 | L.append('') 68 | L.append(filedata) 69 | L.append('--' + BOUNDARY + '--') 70 | L.append('') 71 | 72 | # Issue 1 73 | # http://code.google.com/p/pyamazonclouddrive/issues/detail?id=1 74 | body = CRLF.join([x if type(x)==str else str(x) for x in L]) 75 | 76 | content_type = 'multipart/form-data; boundary=%s' % BOUNDARY 77 | return content_type, body 78 | 79 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | *NOTE*: This is a fork of the PyAmazonCloudDrive project located at http://code.google.com/p/pyamazonclouddrive/. All credit goes to the original author, whose name I believe to be Sakurai Youhei . I just wanted it in git so I could use it in a different project. Although development appears to have stalled, I'll import any changes from the original in the "upstream" branch. 2 | 3 | ---- 4 | 5 | PyAmazonCloudDrive? (pyacd) is 3rd-party Python library for accessing "Amazon Cloud Drive". 6 | 7 | Now this is just an experimental project, but a GOAL is a few line upload-download like "boto". 8 | 9 | ###################################################### 10 | # *WARNING* Amazon Cloud Drive: Terms of Use 11 | ###################################################### 12 | 13 | http://www.amazon.com/gp/help/customer/display.html/?nodeId=200557360 14 | 15 | 6. Software 16 | 17 | you may not ... (f) modify, reverse engineer, decompile or disassemble, or 18 | otherwise tamper with, the Software, whether in whole or in part, or create 19 | any derivative works from or of the Software. 20 | 21 | ###################################################### 22 | # *WARNING* device limit (up to eight devices.) 23 | ###################################################### 24 | 25 | http://www.amazon.com/gp/help/customer/display.html/?ie=UTF8&nodeId=200557340\n 26 | 27 | Frequently Asked Questions 28 | How many devices can I use to access the files I've stored in my Cloud Drive? 29 | 30 | ###################################################### 31 | # Installation of pyacd 32 | ###################################################### 33 | 34 | (1) Checkout repository with svn. (Execute following command.) 35 | 36 | svn checkout http://pyamazonclouddrive.googlecode.com/svn/trunk/pyacd /path/to/your/pylibdir 37 | 38 | (2) If you use python 2.5.x (or lower version one) and 39 | you have not ever used simplejson library, also execute 40 | following one. 41 | 42 | svn checkout http://pyamazonclouddrive.googlecode.com/svn/trunk/simplejson /path/to/your/pylibdir 43 | 44 | (3) Read source in pyacd. 45 | 46 | Use the Source, Luke. 47 | 48 | ###################################################### 49 | # Installation and Usage of acdxxx.py 50 | ###################################################### 51 | 52 | (1) Checkout whole repository with svn. (Execute following command.) 53 | 54 | svn checkout http://pyamazonclouddrive.googlecode.com/svn/trunk/ ~/pyacd 55 | export PATH=$PATH:~/pyacd 56 | 57 | (2) Upload files in somewhere. 58 | 59 | acdmkdir.py -e someone@example.com -p xxxx -s ~/.acdsession somewhere 60 | ls -F somewhere|grep -v /|sed "s/[\*|@]$//g"|sed "s/^/somewhere\//g"|acdput.py -s ~/.acdsession -d somewhere - 61 | 62 | (3) Download files to somewhere_else. 63 | 64 | mkdir somewhere_else 65 | acdlist.py -s ~/.acdsession -t FILE somewhere|sed "s/^/somewhere\//g"|acdget.py -s ~/.acdsession -d somewhere_else - 66 | 67 | (4) See also help. 68 | 69 | acdget.py --help 70 | acdlist.py --help 71 | acdmkdir.py --help 72 | acdrecycle.py --help 73 | acdput.py --help 74 | acdcat.py --help 75 | 76 | (5) Alias if you need. 77 | 78 | alias acdget='acdget.py -s ~/.acdsession -q' 79 | alias acdlist='acdlist.py -s ~/.acdsession -q' 80 | alias acdmkdir='acdmkdir.py -s ~/.acdsession -q' 81 | alias acdrecycle='acdrecycle.py -s ~/.acdsession -q' 82 | alias acdput='acdput.py -s ~/.acdsession -q' 83 | alias acdcat='acdcat.py -s ~/.acdsession -q' 84 | -------------------------------------------------------------------------------- /pyacd/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for *YOUNGER* than you, not *OLDER*. 23 | # 24 | 25 | """Library to access Amazon Cloud Drive 26 | 27 | Amazon Cloud Drive is based on... 28 | 29 | * Authentication: cookie based 30 | * RequestFormat: query strings (e.g. https://....?operation=foo¶m=bar) 31 | * ResponseFormat: JSON 32 | * Entity's data: stored S3 33 | * Entity's metadata: probably stored like following 34 | +-----------+------------------------------------------------------ 35 | |primary key|foreign keys | 36 | +-----------+---------------+-----------+------+-----+--------+---- 37 | |objectId |parentObjectId |customerId | path |name |version |.... 38 | +-----------+---------------+-----------+------+-----+--------+---- 39 | |... |... |... |... |... |... +.... 40 | +-----------+---------------+-----------+------+-----+--------+---- 41 | 42 | You may use following functions and classes... 43 | 44 | * pyacd.login() 45 | * pyacd.api.* 46 | * pyacd.types.* 47 | * pyacd.status.* 48 | * pyacd.PyAmazonCloudDriveApiException 49 | * pyacd.PyAmazonCloudDriveError 50 | 51 | Sample code(Downloading) 52 | ---- 53 | import pyacd 54 | session = pyacd.login("someone@example.com","foobar") 55 | if session and session.is_logged_in(): 56 | fileobj = pyacd.api.get_info_by_path("/path/to/file") 57 | data=pyacd.api.download_by_id(fileobj.object_id) 58 | ---- 59 | 60 | Sample code(Uploading) 61 | ---- 62 | import pyacd 63 | session = pyacd.login("someone@example.com","foobar") 64 | if session and session.is_logged_in(): 65 | fileobj = pyacd.api.create_by_path("/path/to/upload","filename") 66 | data = open("/path/to/file","rb").read() 67 | upload_url = pyacd.api.get_upload_url_by_id(fileobj.object_id,len(data)) 68 | pyacd.api.upload(upload_url.http_request.end_point, 69 | upload_url.http_request.parameters, 70 | "filename",data) 71 | pyacd.api.complete_file_upload_by_id(upload_url.object_id, 72 | upload_url.storage_key) 73 | ---- 74 | """ 75 | 76 | __author__ = "sakurai_youhei" 77 | __copyright__ = "Copyright (c) 2011 anatanokeitai.com(sakurai_youhei)" 78 | __license__ = "MIT" 79 | __version__ = "0.0.6" 80 | __maintainer__ = "sakurai_youhei" 81 | __status__ = "Prototype" 82 | 83 | 84 | from pyacd.exception import * 85 | from pyacd.connection import Connection 86 | from pyacd.multipart import post_multipart 87 | 88 | from pyacd.auth import login 89 | 90 | import types 91 | import status 92 | 93 | import api 94 | 95 | 96 | debug_level=0 97 | conn=Connection() 98 | api_root="https://www.amazon.com/clouddrive/api/" 99 | 100 | def get_session(): 101 | """ Get current session having login status and tokens. 102 | :rtype: :class:`pyacd.session.Session` 103 | :return: Inclues cookies, username and customer_id. 104 | """ 105 | return conn.session 106 | -------------------------------------------------------------------------------- /pyacd/auth.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for *YOUNGER* than you, not *OLDER*. 23 | # 24 | 25 | import re 26 | import urllib 27 | from HTMLParser import HTMLParser 28 | 29 | import pyacd 30 | 31 | def login(email=None,password=None,session=None): 32 | if session: 33 | pyacd.conn.session=Session(session) 34 | elif email is None or password is None: 35 | raise TypeError("invalid args email->%s,password->%s"%(email,password)) 36 | else: 37 | pyacd.conn.session=Session() 38 | 39 | end_point="https://www.amazon.com/clouddrive" 40 | html=pyacd.conn.do_get(end_point) 41 | 42 | if email and password: 43 | begin='
",1)[0] 66 | customer_id=re.sub('.*value="','',customer_id) 67 | customer_id=re.sub('".*','',customer_id) 68 | pyacd.conn.session.customer_id=customer_id 69 | 70 | username=html.split("customer_greeting",1)[1] 71 | username=username.split("<",1)[0] 72 | username=username.split(",")[1][1:] 73 | username=re.sub(r'\..*','',username) 74 | pyacd.conn.session.username=username 75 | except: 76 | pass 77 | 78 | return pyacd.conn.session 79 | 80 | 81 | 82 | 83 | class CustomHTMLParser(HTMLParser): 84 | def __init__(self): 85 | HTMLParser.__init__(self) 86 | self.key_value={} 87 | self.action="" 88 | 89 | def handle_starttag(self, tag, attrs): 90 | d=dict(attrs) 91 | if tag=="form": 92 | #print d 93 | self.action=d.get("action","") 94 | elif tag=='input': 95 | if d.get('name'): 96 | self.key_value[d.get('name')]=d.get('value','') 97 | def handle_endtag(self, tag): 98 | if tag=='input': 99 | pass 100 | 101 | 102 | 103 | 104 | class Session(object): 105 | def __init__(self,session=None): 106 | self.username=None 107 | self.customer_id=None 108 | self.cookies={} 109 | self._initializing=True 110 | if not session: 111 | pyacd.conn.session=self 112 | end_point = "http://www.amazon.com/" 113 | pyacd.conn.do_get(end_point) 114 | pyacd.conn.do_get(end_point) 115 | self._initializing=False 116 | else: 117 | self.cookies.update(session.cookies) 118 | self._initializing=False 119 | 120 | 121 | def __repr__(self): 122 | return '' % ",".join( [ k for k,v in self.cookies.items() ] ) 123 | 124 | def __str__(self): 125 | return '' % ",".join( [ k for k,v in self.cookies.items() ] ) 126 | 127 | def is_valid(self): 128 | if self._initializing: 129 | return True 130 | else: 131 | return self.cookies.has_key("session-id") and \ 132 | self.cookies.has_key("session-id-time") and \ 133 | self.cookies.has_key("ubid-main") 134 | 135 | def is_logged_in(self): 136 | return (self.is_valid() and self.username and self.customer_id) 137 | 138 | def update_cookies(self,cookie_str): 139 | #self.cookies={} 140 | for c in cookie_str.split(", "): 141 | if c.startswith("session-") or c.startswith("ubid-") or c.startswith("x-") or \ 142 | c.startswith("__")or c.startswith("at-"): 143 | self.cookies.update( dict( [re.sub(";.*","",c).split("=",1),] ) ) 144 | 145 | def print_debug(self): 146 | print "*"*20 147 | for k,v in self.cookies.items(): 148 | print "%s=%s"%(k,v) 149 | 150 | -------------------------------------------------------------------------------- /pyacd/connection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for Younger than you, not Older. 23 | # 24 | 25 | import os 26 | import sys 27 | import urllib2 28 | import httplib 29 | import socket 30 | 31 | import pyacd 32 | from pyacd.exception import PyAmazonCloudDriveError 33 | 34 | 35 | class Connection(object): 36 | def __init__(self): 37 | self.session=None 38 | 39 | def do_get(self,url,headers=None): 40 | return self._do_request("GET",url,None,headers) 41 | 42 | def do_delete(self,url,headers=None): 43 | return self._do_request("DELETE",url,None,headers) 44 | 45 | def do_post(self,url,body,headers=None): 46 | return self._do_request("POST",url,body,headers) 47 | 48 | def do_put(self,url,body,headers=None): 49 | return self._do_request("PUT",url,body,headers) 50 | 51 | def _do_request(self,method,url,body,headers): 52 | """ 53 | return response string 54 | """ 55 | if not (method=="GET" or method=="POST" or method=="PUT" or method=="DELETE"): 56 | raise PyAmazonCloudDriveError("unsupported method %s"%method) 57 | 58 | if not self.session: 59 | raise PyAmazonCloudDriveError("session is None.") 60 | elif not self.session.is_valid(): 61 | raise PyAmazonCloudDriveError("session is invalid. %s"%self.session) 62 | 63 | scheme,host = urllib2.urlparse.urlparse(url)[:2] 64 | 65 | conn=gen_httplib_conn(scheme,host) 66 | 67 | hs={"Cookie":"; ".join( ["=".join(i) for i in self.session.cookies.items()] )} 68 | if self.session.cookies.get("session-id"): 69 | hs["x-amzn-SessionId"]=self.session.cookies.get("session-id") 70 | 71 | if headers: 72 | hs.update(headers) 73 | 74 | path = url.split(host,1)[1] 75 | conn.request(method,path,body,hs) 76 | 77 | 78 | # print method 79 | # print path 80 | # print body 81 | # print hs 82 | # print "*"*20 83 | 84 | 85 | if pyacd.debug_level: 86 | sys.stderr.write(method) 87 | 88 | resp = conn.getresponse() 89 | 90 | if 400< resp.status <599: 91 | sys.stderr.write(resp.read()) 92 | raise PyAmazonCloudDriveError("response code is %d"%resp.status) 93 | 94 | if pyacd.debug_level: 95 | sys.stderr.write("->") 96 | 97 | if resp.getheader("Set-Cookie"): 98 | self.session.update_cookies(resp.getheader("Set-Cookie")) 99 | 100 | if resp.getheader("Location"): 101 | return self.do_get(resp.getheader("Location")) 102 | 103 | resp_body=resp.read() 104 | conn.close() 105 | return resp_body 106 | 107 | 108 | def gen_httplib_conn(scheme,host,proxy_host=None,proxy_port=None): 109 | """ 110 | SEE ALSO http://code.activestate.com/recipes/301740-simplest-useful-https-with-basic-proxy-authenticat/ 111 | """ 112 | _port = {'http' : 80, 'https' : 443} 113 | _conn= {'http' : httplib.HTTPConnection, 'https' : httplib.HTTPSConnection} 114 | 115 | if scheme not in _port.keys(): 116 | raise PyAmazonCloudDriveError("unsupported scheme. %s"%scheme) 117 | 118 | if proxy_host and proxy_port: 119 | pass 120 | elif urllib2.getproxies().has_key(scheme): 121 | proxy = urllib2.getproxies()[scheme] 122 | if proxy.find("/")!=-1: 123 | proxy = urllib2.urlparse.urlparse(urllib2.getproxies()[scheme])[1] 124 | if proxy.find(":")!=-1: 125 | proxy_host,proxy_port=proxy.split(":") 126 | else: 127 | proxy_host=proxy 128 | proxy_port=_port[scheme] 129 | 130 | else: 131 | return _conn[scheme](host) 132 | 133 | #print proxy_host,proxy_port 134 | 135 | proxy_connect='CONNECT %s:%s HTTP/1.1\r\n'%(host,_port[scheme]) 136 | proxy_pieces=proxy_connect+'\r\n' 137 | 138 | #print proxy_pieces 139 | 140 | proxy_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 141 | proxy_socket.connect((proxy_host,int(proxy_port))) 142 | proxy_socket.sendall(proxy_pieces) 143 | response=proxy_socket.recv(8192) 144 | status=response.split()[1] 145 | if status!=str(200): 146 | raise PyAmazonCloudDriveError("%s:%s CONNECT returns %s."% 147 | (proxy_host,proxy_port,status)) 148 | 149 | if scheme == 'http': 150 | sock = proxy_socket 151 | else: 152 | if sys.version_info[:2] < (2, 6): 153 | ssl = socket.ssl(proxy_socket, None, None) 154 | sock = httplib.FakeSocket(proxy_socket, ssl) 155 | else: 156 | import ssl 157 | sock = ssl.wrap_socket(proxy_socket, None, None) 158 | 159 | conn=httplib.HTTPConnection(host) 160 | conn.sock=sock 161 | return conn 162 | 163 | 164 | -------------------------------------------------------------------------------- /bin/acdcat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, dis- 9 | # tribute, sublicense, and/or sell copies of the Software, and to permit 10 | # persons to whom the Software is furnished to do so, subject to the fol- 11 | # lowing conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | # IN THE SOFTWARE. 23 | # 24 | # The Software shall be used for Younger than you, not Older. 25 | # 26 | """ 27 | administrator@Tualatin ~/svn/pyacd $ ./acdcat.py --help 28 | Usage: acdcat.py [Options] file1 file2 - ...('-' means STDIN) 29 | 30 | Options: 31 | --version show program's version number and exit 32 | -h, --help show this help message and exit 33 | -e EMAIL, --email=EMAIL 34 | email address for Amazon.com 35 | -p PASSWORD, --password=PASSWORD 36 | password for Amazon.com 37 | -s FILE, --session=FILE 38 | save or load login session to/from FILE 39 | -v, --verbose show debug infomation 40 | -q, --quiet quiet mode 41 | 42 | This command shows file(s) from your Amazon Cloud Drive. 43 | """ 44 | 45 | import sys 46 | import os 47 | from optparse import OptionParser 48 | import pickle 49 | 50 | pyacd_lib_dir=os.path.dirname(os.__file__)+os.sep+"pyacd" 51 | if os.path.exists(pyacd_lib_dir) and os.path.isdir(pyacd_lib_dir): 52 | sys.path.insert(0, pyacd_lib_dir) 53 | 54 | import pyacd 55 | 56 | parser=OptionParser(epilog="This command shows file(s) from your Amazon Cloud Drive. ", 57 | usage="%prog [Options] file1 file2 - ...('-' means STDIN)",version="%prog 0.2") 58 | 59 | parser.add_option("-e","--email",dest="email",action="store",default=None, 60 | help="email address for Amazon.com") 61 | parser.add_option("-p","--password",dest="password",action="store",default=None, 62 | help="password for Amazon.com") 63 | parser.add_option("-s","--session",dest="session",action="store",default=None, 64 | metavar="FILE",help="save or load login session to/from FILE") 65 | parser.add_option("-v","--verbose",dest="verbose",action="store_true",default=False, 66 | help="show debug infomation") 67 | parser.add_option("-q","--quiet",dest="quiet",action="store_true",default=False, 68 | help="quiet mode") 69 | 70 | def main(): 71 | opts,args=parser.parse_args(sys.argv[1:]) 72 | 73 | # Check options of authentication 74 | if opts.session and os.path.exists(opts.session) and not os.path.isdir(opts.session): 75 | pass 76 | elif not opts.email or not opts.password: 77 | sys.stderr.write("!! email and password are required !!\n") 78 | parser.print_help() 79 | sys.exit(2) 80 | 81 | args=list(set(args)) 82 | if "-" in args: 83 | args.remove("-") 84 | args += [x.strip() for x in sys.stdin.readlines()] 85 | 86 | if 0==len(args): 87 | sys.stderr.write("!! no file selected !!\n") 88 | parser.print_help() 89 | sys.exit(2) 90 | else: 91 | pass 92 | 93 | # Login to Amazon.com 94 | session=None 95 | try: 96 | if not opts.quiet: 97 | sys.stderr.write("Logining to Amazon.com ... ") 98 | if opts.email and opts.password: 99 | session=pyacd.login(opts.email,opts.password) 100 | else: 101 | fp=open(opts.session,"rb") 102 | session=pickle.load(fp) 103 | fp.close() 104 | session=pyacd.login(session=session) 105 | except: 106 | pass 107 | 108 | # Check login status 109 | if not session: 110 | sys.stderr.write("Unexpected error occured.\n") 111 | sys.exit(2) 112 | elif not session.is_valid(): 113 | sys.stderr.write("Session is invalid.\n%s\n"%session) 114 | sys.exit(2) 115 | elif not session.is_logged_in(): 116 | sys.stderr.write("Login failed.\n%s\n"%session) 117 | sys.exit(2) 118 | 119 | if not opts.quiet: 120 | sys.stderr.write("Done\n") 121 | 122 | if opts.session: 123 | if not opts.quiet: 124 | sys.stderr.write("Updating %s ... "%opts.session) 125 | fp=open(opts.session,"wb") 126 | fp.truncate() 127 | pickle.dump(session,fp) 128 | fp.close() 129 | if not opts.quiet: 130 | sys.stderr.write("Done\n") 131 | 132 | for file in args: 133 | if file[0]!='/':file='/'+file 134 | filename = os.path.basename(file) 135 | 136 | if not opts.quiet: 137 | sys.stderr.write("Downloading %s ... "%(file)) 138 | 139 | # get file 140 | if opts.verbose: 141 | sys.stderr.write("get ") 142 | try: 143 | fileobj = pyacd.api.get_info_by_path(file) 144 | except pyacd.PyAmazonCloudDriveApiException,e: 145 | sys.stderr.write("Aborted. ('%s')\n"%e.message) 146 | continue 147 | if opts.verbose: 148 | sys.stderr.write("-> ") 149 | 150 | if fileobj.Type!= pyacd.types.FILE: 151 | sys.stderr.write("Aborted. ('%s' is not file.)"%file) 152 | continue 153 | 154 | # download 155 | data=pyacd.api.download_by_id(fileobj.object_id) 156 | 157 | if not opts.quiet: 158 | sys.stderr.write("Done\n") 159 | 160 | sys.stdout.write(data) 161 | 162 | if __name__=="__main__": 163 | main() 164 | -------------------------------------------------------------------------------- /bin/acdmkdir: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, dis- 9 | # tribute, sublicense, and/or sell copies of the Software, and to permit 10 | # persons to whom the Software is furnished to do so, subject to the fol- 11 | # lowing conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | # IN THE SOFTWARE. 23 | # 24 | # The Software shall be used for Younger than you, not Older. 25 | # 26 | """ 27 | administrator@Tualatin ~/svn/pyacd $ ./acdmkdir.py --help 28 | Usage: acdmkdir.py [Options] dir1 dir2 - ...('-' means STDIN) 29 | 30 | Options: 31 | --version show program's version number and exit 32 | -h, --help show this help message and exit 33 | -e EMAIL, --email=EMAIL 34 | email address for Amazon.com 35 | -p PASSWORD, --password=PASSWORD 36 | password for Amazon.com 37 | -s FILE, --session=FILE 38 | save or load login session to/from FILE 39 | -v, --verbose show debug infomation 40 | -q, --quiet quiet mode 41 | 42 | This command makes dir(s) in your Amazon Cloud Drive. If there is same named 43 | dir, making dir is aborted automatically. 44 | 45 | administrator@Tualatin ~/svn/pyacd $ ./acdmkdir.py -s ~/.session testdir 46 | Logining to Amazon.com ... Done 47 | Updating /home/administratora/.session ... Done 48 | Making testdir in / ... Done 49 | """ 50 | 51 | import sys 52 | import os 53 | from optparse import OptionParser 54 | import pickle 55 | 56 | pyacd_lib_dir=os.path.dirname(os.__file__)+os.sep+"pyacd" 57 | if os.path.exists(pyacd_lib_dir) and os.path.isdir(pyacd_lib_dir): 58 | sys.path.insert(0, pyacd_lib_dir) 59 | 60 | import pyacd 61 | 62 | parser=OptionParser(epilog="This command makes dir(s) in your Amazon Cloud Drive. "+ 63 | "If there is same named dir, making dir is aborted "+ 64 | "automatically.", 65 | usage="%prog [Options] dir1 dir2 - ...('-' means STDIN)",version="%prog 0.2") 66 | 67 | parser.add_option("-e","--email",dest="email",action="store",default=None, 68 | help="email address for Amazon.com") 69 | parser.add_option("-p","--password",dest="password",action="store",default=None, 70 | help="password for Amazon.com") 71 | parser.add_option("-s","--session",dest="session",action="store",default=None, 72 | metavar="FILE",help="save or load login session to/from FILE") 73 | parser.add_option("-v","--verbose",dest="verbose",action="store_true",default=False, 74 | help="show debug infomation") 75 | parser.add_option("-q","--quiet",dest="quiet",action="store_true",default=False, 76 | help="quiet mode") 77 | 78 | def main(): 79 | opts,args=parser.parse_args(sys.argv[1:]) 80 | 81 | # Check options of authentication 82 | if opts.session and os.path.exists(opts.session) and not os.path.isdir(opts.session): 83 | pass 84 | elif not opts.email or not opts.password: 85 | sys.stderr.write("!! email and password are required !!\n") 86 | parser.print_help() 87 | sys.exit(2) 88 | 89 | args=list(set(args)) 90 | if "-" in args: 91 | args.remove("-") 92 | args += [x.strip() for x in sys.stdin.readlines()] 93 | 94 | if 0==len(args): 95 | sys.stderr.write("!! no dir selected !!\n") 96 | parser.print_help() 97 | sys.exit(2) 98 | else: 99 | pass 100 | 101 | # Login to Amazon.com 102 | session=None 103 | try: 104 | if not opts.quiet: 105 | sys.stderr.write("Logining to Amazon.com ... ") 106 | if opts.email and opts.password: 107 | session=pyacd.login(opts.email,opts.password) 108 | else: 109 | fp=open(opts.session,"rb") 110 | session=pickle.load(fp) 111 | fp.close() 112 | session=pyacd.login(session=session) 113 | except: 114 | pass 115 | 116 | # Check login status 117 | if not session: 118 | sys.stderr.write("Unexpected error occured.\n") 119 | sys.exit(2) 120 | elif not session.is_valid(): 121 | sys.stderr.write("Session is invalid.\n%s\n"%session) 122 | sys.exit(2) 123 | elif not session.is_logged_in(): 124 | sys.stderr.write("Login failed.\n%s\n"%session) 125 | sys.exit(2) 126 | 127 | if not opts.quiet: 128 | sys.stderr.write("Done\n") 129 | 130 | if opts.session: 131 | if not opts.quiet: 132 | sys.stderr.write("Updating %s ... "%opts.session) 133 | fp=open(opts.session,"wb") 134 | fp.truncate() 135 | pickle.dump(session,fp) 136 | fp.close() 137 | if not opts.quiet: 138 | sys.stderr.write("Done\n") 139 | 140 | for path in args: 141 | if path[0]!='/':path='/'+path 142 | folder = path.split("/")[-1] 143 | parent = "/".join(path.split("/")[:-1]) 144 | parent = parent if len(parent)!=0 else "/" 145 | 146 | if not opts.quiet: 147 | sys.stderr.write("Making %s in %s ... "%(folder,parent)) 148 | 149 | # create folder 150 | if opts.verbose: 151 | sys.stderr.write("create ") 152 | try: 153 | pyacd.api.create_by_path(parent,folder,Type=pyacd.types.FOLDER) 154 | except pyacd.PyAmazonCloudDriveApiException,e: 155 | sys.stderr.write("Aborted. ('%s')\n"%e.message) 156 | continue 157 | if opts.verbose: 158 | sys.stderr.write("-> ") 159 | 160 | if not opts.quiet: 161 | sys.stderr.write("Done\n") 162 | 163 | 164 | if __name__=="__main__": 165 | main() 166 | -------------------------------------------------------------------------------- /bin/acdrecycle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, dis- 9 | # tribute, sublicense, and/or sell copies of the Software, and to permit 10 | # persons to whom the Software is furnished to do so, subject to the fol- 11 | # lowing conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | # IN THE SOFTWARE. 23 | # 24 | # The Software shall be used for Younger than you, not Older. 25 | # 26 | """ 27 | administrator@Tualatin ~/svn/pyacd $ ./acdrecycle.py --help 28 | Usage: acdrecycle.py [Options] path1 path2 - ...('-' means STDIN) 29 | 30 | Options: 31 | --version show program's version number and exit 32 | -h, --help show this help message and exit 33 | -e EMAIL, --email=EMAIL 34 | email address for Amazon.com 35 | -p PASSWORD, --password=PASSWORD 36 | password for Amazon.com 37 | -s FILE, --session=FILE 38 | save or load login session to/from FILE 39 | -v, --verbose show debug infomation 40 | -q, --quiet quiet mode 41 | 42 | This command move file(s) or dir(s) to Recycle of your Amazon Cloud Drive. 43 | 44 | administrator@Tualatin ~/svn/pyacd $ ./acdrecycle.py -s ~/.session README.TXT 45 | Logining to Amazon.com ... Done 46 | Updating /home/administrator/.session ... Done 47 | Moving /README.TXT to Recycle ... Done 48 | """ 49 | 50 | import sys 51 | import os 52 | from optparse import OptionParser 53 | import pickle 54 | 55 | pyacd_lib_dir=os.path.dirname(os.__file__)+os.sep+"pyacd" 56 | if os.path.exists(pyacd_lib_dir) and os.path.isdir(pyacd_lib_dir): 57 | sys.path.insert(0, pyacd_lib_dir) 58 | 59 | import pyacd 60 | 61 | parser=OptionParser(epilog="This command move file(s) or dir(s) to Recycle "+ 62 | "of your Amazon Cloud Drive. ", 63 | usage="%prog [Options] path1 path2 - ...('-' means STDIN)",version="%prog 0.2") 64 | 65 | parser.add_option("-e","--email",dest="email",action="store",default=None, 66 | help="email address for Amazon.com") 67 | parser.add_option("-p","--password",dest="password",action="store",default=None, 68 | help="password for Amazon.com") 69 | parser.add_option("-s","--session",dest="session",action="store",default=None, 70 | metavar="FILE",help="save or load login session to/from FILE") 71 | parser.add_option("-v","--verbose",dest="verbose",action="store_true",default=False, 72 | help="show debug infomation") 73 | parser.add_option("-q","--quiet",dest="quiet",action="store_true",default=False, 74 | help="quiet mode") 75 | 76 | def main(): 77 | opts,args=parser.parse_args(sys.argv[1:]) 78 | 79 | # Check options of authentication 80 | if opts.session and os.path.exists(opts.session) and not os.path.isdir(opts.session): 81 | pass 82 | elif not opts.email or not opts.password: 83 | sys.stderr.write("!! email and password are required !!\n") 84 | parser.print_help() 85 | sys.exit(2) 86 | 87 | args=list(set(args)) 88 | if "-" in args: 89 | args.remove("-") 90 | args += [x.strip() for x in sys.stdin.readlines()] 91 | 92 | if 0==len(args): 93 | sys.stderr.write("!! no path selected !!\n") 94 | parser.print_help() 95 | sys.exit(2) 96 | else: 97 | pass 98 | 99 | # Login to Amazon.com 100 | session=None 101 | try: 102 | if not opts.quiet: 103 | sys.stderr.write("Logining to Amazon.com ... ") 104 | if opts.email and opts.password: 105 | session=pyacd.login(opts.email,opts.password) 106 | else: 107 | fp=open(opts.session,"rb") 108 | session=pickle.load(fp) 109 | fp.close() 110 | session=pyacd.login(session=session) 111 | except: 112 | pass 113 | 114 | # Check login status 115 | if not session: 116 | sys.stderr.write("Unexpected error occured.\n") 117 | sys.exit(2) 118 | elif not session.is_valid(): 119 | sys.stderr.write("Session is invalid.\n%s\n"%session) 120 | sys.exit(2) 121 | elif not session.is_logged_in(): 122 | sys.stderr.write("Login failed.\n%s\n"%session) 123 | sys.exit(2) 124 | 125 | if not opts.quiet: 126 | sys.stderr.write("Done\n") 127 | 128 | if opts.session: 129 | if not opts.quiet: 130 | sys.stderr.write("Updating %s ... "%opts.session) 131 | fp=open(opts.session,"wb") 132 | fp.truncate() 133 | pickle.dump(session,fp) 134 | fp.close() 135 | if not opts.quiet: 136 | sys.stderr.write("Done\n") 137 | 138 | for path in args: 139 | if path[0]!='/':path='/'+path 140 | 141 | if not opts.quiet: 142 | sys.stderr.write("Moving %s to Recycle ... "%(path)) 143 | 144 | # get path 145 | if opts.verbose: 146 | sys.stderr.write("get ") 147 | try: 148 | pathobj = pyacd.api.get_info_by_path(path) 149 | except pyacd.PyAmazonCloudDriveApiException,e: 150 | sys.stderr.write("Aborted. ('%s')\n"%e.message) 151 | continue 152 | if opts.verbose: 153 | sys.stderr.write("-> ") 154 | 155 | if pathobj.Type!= pyacd.types.FILE and pathobj.Type!= pyacd.types.FOLDER : 156 | sys.stderr.write("Aborted. ('%s<%s>' is special entity.)"%(path,pathobj.Type)) 157 | continue 158 | 159 | # move 160 | if opts.verbose: 161 | sys.stderr.write("move ") 162 | pyacd.api.recycle_bulk_by_id([pathobj.object_id,]) 163 | if opts.verbose: 164 | sys.stderr.write("-> ") 165 | 166 | if not opts.quiet: 167 | sys.stderr.write("Done\n") 168 | 169 | 170 | if __name__=="__main__": 171 | main() 172 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, dis- 9 | # tribute, sublicense, and/or sell copies of the Software, and to permit 10 | # persons to whom the Software is furnished to do so, subject to the fol- 11 | # lowing conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | # IN THE SOFTWARE. 23 | # 24 | # The Software shall be used for *YOUNGER* than you, not *OLDER*. 25 | # 26 | 27 | import sys 28 | import unittest 29 | from cStringIO import StringIO 30 | #from ConfigParser import SafeConfigParser 31 | import time 32 | import xml.dom.minidom 33 | 34 | import pyacd 35 | pyacd.debug_level=2 36 | 37 | if len(sys.argv)!=3: 38 | sys.stderr.write("usage: ./test.py email password") 39 | sys.exit(2) 40 | 41 | email=sys.argv[1] 42 | password=sys.argv[2] 43 | 44 | print "*** Inputs are here ***" 45 | print "email:",email 46 | print "password:",password 47 | print "*"*20 48 | print "" 49 | 50 | session=None 51 | 52 | class AuthTest(unittest.TestCase): 53 | def setUp(self): 54 | pass 55 | 56 | def tearDown(self): 57 | pass 58 | 59 | def testLogin(self): 60 | global session 61 | session=pyacd.login(email,password) 62 | self.assertTrue(session.is_valid(),"invalid session %s"%session) 63 | self.assertTrue(session.is_logged_in(),"not logined %s"%session) 64 | self.assertNotEqual(session.username,None,"username is None %s"%session) 65 | self.assertNotEqual(session.customer_id,None,"customer_id is None %s"%session) 66 | #sys.stderr.write(str(session)) 67 | 68 | """ 69 | def testLoginWithNoneEmail(self): 70 | global session 71 | try: 72 | session=pyacd.login(None,password) 73 | except TypeError,e: 74 | pass 75 | 76 | def testLoginWithNonePassword(self): 77 | global session 78 | try: 79 | session=pyacd.login(email,None) 80 | except TypeError,e: 81 | pass 82 | 83 | def testLoginWithNoneArgs(self): 84 | global session 85 | try: 86 | session=pyacd.login(None,None,None) 87 | except TypeError,e: 88 | pass 89 | 90 | def testReloginWithSession(self): 91 | global session 92 | session=pyacd.login(session=session) 93 | self.assertTrue(session.is_valid(),"invalid session %s"%session) 94 | self.assertTrue(session.is_logged_in(),"not logined %s"%session) 95 | self.assertNotEqual(session.username,None,"username is None %s"%session) 96 | self.assertNotEqual(session.customer_id,None,"customer_id is None %s"%session) 97 | """ 98 | 99 | class ApiTest(unittest.TestCase): 100 | def setUp(self): 101 | pass 102 | 103 | def tearDown(self): 104 | pass 105 | 106 | def testUserStorage(self): 107 | user_storage = pyacd.api.get_user_storage() 108 | self.assertEqual(user_storage.total_space,user_storage. 109 | used_space+user_storage.free_space,"total /= used+free %s"%user_storage) 110 | #sys.stderr.write(str(user_storage)) 111 | 112 | def testSubscriptionProblem(self): 113 | subscription_problem=pyacd.api.get_subscription_problem() 114 | #sys.stderr.write(str(subscription_problem)) 115 | 116 | def testInfoByPathAndById(self): 117 | info_by_path=pyacd.api.get_info_by_path("/") 118 | #sys.stderr.write(str(info_by_path)) 119 | info_by_id=pyacd.api.get_info_by_id(info_by_path.object_id) 120 | #sys.stderr.write(str(info_by_path)) 121 | self.assertEqual(info_by_path.name,info_by_id.name,"different from byPath(%s) and byId(%s)"% 122 | (info_by_path,info_by_id)) 123 | 124 | def testListById(self): 125 | info=pyacd.api.get_info_by_path("/") 126 | pyacd.api.list_by_id(info.object_id) 127 | 128 | def testFolder_Create_Rename_Copy_Recycle_Remove(self): 129 | root=pyacd.api.get_info_by_path("/") 130 | old_name="create_%d"%int(time.time()) 131 | new_name=old_name.replace("create","rename") 132 | 133 | # folder1 create(old_name) -> rename(new_name) 134 | folder1=pyacd.api.create_by_id(root.object_id,old_name) 135 | pyacd.api.move_by_id(folder1.object_id,root.object_id,new_name) 136 | 137 | # folder2 create(old_name) -> copy to new_name/ move to new_name/ 138 | folder2=pyacd.api.create_by_id(root.object_id,old_name) 139 | pyacd.api.copy_bulk_by_id(folder1.object_id,[folder2.object_id,]) 140 | pyacd.api.move_bulk_by_id(folder1.object_id,[folder2.object_id,]) 141 | 142 | # folder1 recycle -> remove 143 | pyacd.api.recycle_bulk_by_id([folder1.object_id,]) 144 | pyacd.api.remove_bulk_by_id([folder1.object_id,]) 145 | 146 | def testEmptyRecycleBin(self): 147 | pyacd.api.empty_recycle_bin() 148 | 149 | def testFile_Create_Upload_Download(self): 150 | filename = "test_%d.txt"%int(time.time()) 151 | filedata = "12345" 152 | 153 | # file1 create 154 | file1 = pyacd.api.create_by_path("/",filename) 155 | 156 | # get upload_url 157 | upload_url = pyacd.api.get_upload_url_by_id(file1.object_id,len(filedata)) 158 | storage_key=upload_url.storage_key 159 | object_id=upload_url.object_id 160 | end_point=upload_url.http_request.end_point 161 | parameters=upload_url.http_request.parameters 162 | 163 | # upload file 164 | pyacd.api.upload(end_point,parameters,filename,filedata) 165 | 166 | # completeing file 167 | pyacd.api.complete_file_upload_by_id(object_id,storage_key) 168 | 169 | # download file 170 | download_data=pyacd.api.download_by_id(object_id) 171 | 172 | self.assertEqual(filedata,download_data,"different from upload and download") 173 | 174 | 175 | 176 | def main(): 177 | suites=[] 178 | 179 | suites.append(unittest.TestLoader().loadTestsFromTestCase(AuthTest)) 180 | suites.append(unittest.TestLoader().loadTestsFromTestCase(ApiTest)) 181 | 182 | runner = unittest.TextTestRunner(verbosity=2) 183 | 184 | for s in suites: 185 | runner.run(s) 186 | 187 | # suite=unittest.TestSuite(suites) 188 | # runner.run(suite) 189 | 190 | 191 | if __name__=="__main__": 192 | main() 193 | 194 | 195 | -------------------------------------------------------------------------------- /pyacd/apiresponse.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for Younger than you, not Older. 23 | # 24 | 25 | 26 | class UploadUrl(object): 27 | def __init__(self,result_json): 28 | self.object_id=result_json.get("objectId") 29 | self.path=result_json.get("path") 30 | self.storage_key=result_json.get("storageKey") 31 | self.http_request=HttpRequest(result_json.get("httpRequest")) 32 | 33 | def __repr__(self): 34 | return '' % (self.object_id) 35 | 36 | def __str__(self): 37 | return '' % (self.object_id) 38 | 39 | class HttpRequest(object): 40 | def __init__(self,result_json): 41 | self.headers=result_json.get("headers") 42 | self.end_point=result_json.get("endpoint") 43 | self.method=result_json.get("methodName") 44 | self.resource_path=result_json.get("resourcePath") 45 | self.parameters=result_json.get("parameters") 46 | #print self.parameters.keys() 47 | 48 | 49 | def __repr__(self): 50 | return '' % (self.method,self.end_point) 51 | 52 | def __str__(self): 53 | return '' % (self.method,self.end_point) 54 | 55 | 56 | class List(object): 57 | def __init__(self,result_json): 58 | self.next_token=result_json.get("nextToken") 59 | self.parent_modified=result_json.get("parentLastUpdated") 60 | self.objects=[] 61 | for obj in result_json.get("objects"): 62 | self.objects.append(Info(obj)) 63 | 64 | def __repr__(self): 65 | return '' % (len(self.objects)) 66 | 67 | def __str__(self): 68 | return '' % (len(self.objects)) 69 | 70 | class Metadata(object): 71 | def __init__(self,result_json): 72 | self.items=result_json.get("items") 73 | 74 | def __repr__(self): 75 | return '' % (len(self.items)) 76 | 77 | def __str__(self): 78 | return '' % (len(self.items)) 79 | 80 | class Info(object): 81 | def __init__(self,result_json): 82 | self.parent_object_id=result_json.get("parentObjectId") 83 | self.status=result_json.get("status") 84 | self.purchased=result_json.get("purchaseDate") 85 | self.size=result_json.get("size") 86 | self.object_id=result_json.get("objectId") 87 | self.storage_system=result_json.get("storageSystem") 88 | self.version=result_json.get("version") 89 | self.hidden=result_json.get("hidden") 90 | self.md5=result_json.get("md5") 91 | self.Type=result_json.get("type") 92 | self.name=result_json.get("name") 93 | self.path=result_json.get("path") 94 | if len(self.name) and self.path: 95 | self.path=self.path[:-1*len(self.name)] 96 | self.created=result_json.get("creationDate") 97 | self.parent_path_before_recycle=result_json.get("parentPathBeforeRecycle") 98 | self.modified=result_json.get("lastUpdatedDate") 99 | # Unknown usage 100 | # keyName,asin,metadata,orderId,permissions,extension,localFilePath 101 | 102 | if self.storage_system: 103 | self.storage_system=StorageSystem(self.storage_system) 104 | #if self.purchased: 105 | # time.asctime(time.localtime(self.purchased)) 106 | #if self.created: 107 | # time.asctime(time.localtime(self.purchased)) 108 | #if self.modified: 109 | # time.asctime(time.localtime(self.purchased)) 110 | 111 | def __eq__(self, other): 112 | return type(self) == type(other) and hash(self) == hash(other) 113 | 114 | def __hash__(self): 115 | return hash((self.name, self.Type, self.path, self.md5, self.modified)) 116 | 117 | def __repr__(self): 118 | return '' % (self.path,self.name,self.Type,self.status,self.version) 119 | 120 | def __str__(self): 121 | return '' % (self.path,self.name,self.Type,self.status,self.version) 122 | 123 | class StorageSystem(object): 124 | def __init__(self,result_json): 125 | self.encrypted=result_json.get("encrypted") 126 | self.storage_key=result_json.get("storageKey") 127 | self.payer_id=result_json.get("payerId") 128 | self.Type=result_json.get("type") 129 | 130 | def __repr__(self): 131 | return '' % (self.encrypted,self.payer_id,self.Type) 132 | 133 | def __str__(self): 134 | return '' % (self.encrypted,self.payer_id,self.Type) 135 | 136 | class UserStorage(object): 137 | def __init__(self,result_json): 138 | self.total_space=result_json.get("totalSpace") 139 | self.used_space=result_json.get("usedSpace") 140 | self.free_space=result_json.get("freeSpace") 141 | if not self.total_space or not self.used_space or not self.free_space: 142 | raise pyacd.PyAmazonCloudDriveError("unexpected response %s"%str(result_json)) 143 | 144 | def __repr__(self): 145 | return '' % ( 146 | self.total_space,self.used_space,self.free_space) 147 | 148 | def __str__(self): 149 | return '' % ( 150 | self.total_space,self.used_space,self.free_space) 151 | 152 | class SubscriptionProblem(object): 153 | def __init__(self,result_json): 154 | self.previous_plan_detail = result_json.get("previousPlanDetail") 155 | self.problem_code = result_json.get("problemCode") 156 | self.target_plan_id = result_json.get("targetPlanId") 157 | self.transaction_type = result_json.get("transactionType") 158 | 159 | def __repr__(self): 160 | return '' % ( 161 | self.previous_plan_detail,self.problem_code, 162 | self.target_plan_id,self.transaction_type) 163 | 164 | def __str__(self): 165 | return '' % ( 166 | self.previous_plan_detail,self.problem_code, 167 | self.target_plan_id,self.transaction_type) 168 | -------------------------------------------------------------------------------- /bin/acdget: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, dis- 9 | # tribute, sublicense, and/or sell copies of the Software, and to permit 10 | # persons to whom the Software is furnished to do so, subject to the fol- 11 | # lowing conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | # IN THE SOFTWARE. 23 | # 24 | # The Software shall be used for Younger than you, not Older. 25 | # 26 | """ 27 | administrator@Tualatin ~/svn/pyacd $ ./acdget.py --help 28 | Usage: acdget.py [Options] file1 file2 - ...('-' means STDIN) 29 | 30 | Options: 31 | --version show program's version number and exit 32 | -h, --help show this help message and exit 33 | -e EMAIL, --email=EMAIL 34 | email address for Amazon.com 35 | -p PASSWORD, --password=PASSWORD 36 | password for Amazon.com 37 | -s FILE, --session=FILE 38 | save or load login session to/from FILE 39 | -d PATH, --destination=PATH 40 | download path [default: ./] 41 | -f, --force override local file if it has same name [default: 42 | False] 43 | -v, --verbose show debug infomation 44 | -q, --quiet quiet mode 45 | 46 | This command download file(s) from your Amazon Cloud Drive. If there is same 47 | named file, downloading is canceled automatically. (or use --force option) 48 | 49 | administrator@Tualatin ~/svn/pyacd $ ./acdget.py -s ~/.session README.TXT 50 | Logining to Amazon.com ... Done 51 | Updating /home/administrator/.session ... Done 52 | Downloading /README.TXT to ./ ... Done 53 | """ 54 | 55 | import sys 56 | import os 57 | from optparse import OptionParser 58 | import pickle 59 | 60 | pyacd_lib_dir=os.path.dirname(os.__file__)+os.sep+"pyacd" 61 | if os.path.exists(pyacd_lib_dir) and os.path.isdir(pyacd_lib_dir): 62 | sys.path.insert(0, pyacd_lib_dir) 63 | 64 | import pyacd 65 | 66 | parser=OptionParser(epilog="This command download file(s) from your Amazon Cloud Drive. "+ 67 | "If there is same named file, downloading is canceled "+ 68 | "automatically. (or use --force option)", 69 | usage="%prog [Options] file1 file2 - ...('-' means STDIN)",version="%prog 0.2") 70 | 71 | parser.add_option("-e","--email",dest="email",action="store",default=None, 72 | help="email address for Amazon.com") 73 | parser.add_option("-p","--password",dest="password",action="store",default=None, 74 | help="password for Amazon.com") 75 | parser.add_option("-s","--session",dest="session",action="store",default=None, 76 | metavar="FILE",help="save or load login session to/from FILE") 77 | parser.add_option("-d","--destination",dest="path",action="store",default="."+os.sep, 78 | help="download path [default: %default]") 79 | parser.add_option("-f","--force",dest="force",action="store_true",default=False, 80 | help="override local file if it has same name [default: %default]") 81 | parser.add_option("-v","--verbose",dest="verbose",action="store_true",default=False, 82 | help="show debug infomation") 83 | parser.add_option("-q","--quiet",dest="quiet",action="store_true",default=False, 84 | help="quiet mode") 85 | 86 | def main(): 87 | opts,args=parser.parse_args(sys.argv[1:]) 88 | 89 | # Check options of authentication 90 | if opts.session and os.path.exists(opts.session) and not os.path.isdir(opts.session): 91 | pass 92 | elif not opts.email or not opts.password: 93 | sys.stderr.write("!! email and password are required !!\n") 94 | parser.print_help() 95 | sys.exit(2) 96 | 97 | args=list(set(args)) 98 | if "-" in args: 99 | args.remove("-") 100 | args += [x.strip() for x in sys.stdin.readlines()] 101 | 102 | if 0==len(args): 103 | sys.stderr.write("!! no file selected !!\n") 104 | parser.print_help() 105 | sys.exit(2) 106 | else: 107 | pass 108 | 109 | # Check destination 110 | path=opts.path 111 | if path[-1]!=os.sep:path=path+os.sep 112 | if not os.path.exists(path): 113 | sys.stderr.write('"%s" does not exist\n'%path) 114 | sys.exit(2) 115 | elif not os.path.isdir(path): 116 | sys.stderr.write('"%s" is not file\n'%path) 117 | sys.exit(2) 118 | 119 | # Login to Amazon.com 120 | session=None 121 | try: 122 | if not opts.quiet: 123 | sys.stderr.write("Logining to Amazon.com ... ") 124 | if opts.email and opts.password: 125 | session=pyacd.login(opts.email,opts.password) 126 | else: 127 | fp=open(opts.session,"rb") 128 | session=pickle.load(fp) 129 | fp.close() 130 | session=pyacd.login(session=session) 131 | except: 132 | pass 133 | 134 | # Check login status 135 | if not session: 136 | sys.stderr.write("Unexpected error occured.\n") 137 | sys.exit(2) 138 | elif not session.is_valid(): 139 | sys.stderr.write("Session is invalid.\n%s\n"%session) 140 | sys.exit(2) 141 | elif not session.is_logged_in(): 142 | sys.stderr.write("Login failed.\n%s\n"%session) 143 | sys.exit(2) 144 | 145 | if not opts.quiet: 146 | sys.stderr.write("Done\n") 147 | 148 | if opts.session: 149 | if not opts.quiet: 150 | sys.stderr.write("Updating %s ... "%opts.session) 151 | fp=open(opts.session,"wb") 152 | fp.truncate() 153 | pickle.dump(session,fp) 154 | fp.close() 155 | if not opts.quiet: 156 | sys.stderr.write("Done\n") 157 | 158 | for file in args: 159 | if file[0]!='/':file='/'+file 160 | filename = os.path.basename(file) 161 | 162 | if not opts.quiet: 163 | sys.stderr.write("Downloading %s to %s ... "%(file,path)) 164 | 165 | if os.path.exists(path+filename) and not opts.force: 166 | sys.stderr.write("Aborted. ('%s' exists.)\n"%(path+filename)) 167 | continue 168 | 169 | # get file 170 | if opts.verbose: 171 | sys.stderr.write("get ") 172 | try: 173 | fileobj = pyacd.api.get_info_by_path(file) 174 | except pyacd.PyAmazonCloudDriveApiException,e: 175 | sys.stderr.write("Aborted. ('%s')\n"%e.message) 176 | continue 177 | if opts.verbose: 178 | sys.stderr.write("-> ") 179 | 180 | if fileobj.Type!= pyacd.types.FILE: 181 | sys.stderr.write("Aborted. ('%s' is not file.)"%file) 182 | continue 183 | 184 | # download 185 | data=pyacd.api.download_by_id(fileobj.object_id) 186 | 187 | 188 | f=open(path+filename,"wb") 189 | f.truncate() 190 | f.write(data) 191 | f.close() 192 | 193 | if not opts.quiet: 194 | sys.stderr.write("Done\n") 195 | 196 | 197 | if __name__=="__main__": 198 | main() 199 | -------------------------------------------------------------------------------- /bin/acdlist: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, dis- 9 | # tribute, sublicense, and/or sell copies of the Software, and to permit 10 | # persons to whom the Software is furnished to do so, subject to the fol- 11 | # lowing conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | # IN THE SOFTWARE. 23 | # 24 | # The Software shall be used for Younger than you, not Older. 25 | # 26 | """ 27 | administrator@Tualatin ~/svn/pyacd $ ./acdlist.py --help 28 | Usage: acdlist.py [Options] path1 path2 - ...('-' means STDIN) 29 | 30 | Options: 31 | --version show program's version number and exit 32 | -h, --help show this help message and exit 33 | -e EMAIL, --email=EMAIL 34 | email address for Amazon.com 35 | -p PASSWORD, --password=PASSWORD 36 | password for Amazon.com 37 | -s FILE, --session=FILE 38 | save or load login session to/from FILE 39 | -l use a long listing format 40 | -t TYPE, --type=TYPE list type (ALL|FILE|FOLDER) [default: ALL] 41 | -v, --verbose show debug infomation 42 | -q, --quiet quiet mode 43 | 44 | This command lists files and directories of your Amazon Cloud Drive. 45 | 46 | administrator@Tualatin ~/svn/pyacd $ ./acdlist.py -s ~/.session / -l 47 | Logining to Amazon.com ... Done 48 | Updating /home/administrator/.session ... Done 49 | Listing / ... Done 50 | total 2 (/) 51 | ==modified========== ==size/type== ==version== ==name========== 52 | 2011-04-24T23:06:00 121266 (5) test.jpg 53 | 2011-04-24T23:06:15 116236 (3) test (2).jpg 54 | """ 55 | 56 | import sys 57 | import os 58 | from optparse import OptionParser 59 | import pickle 60 | import time,datetime 61 | 62 | pyacd_lib_dir=os.path.dirname(os.__file__)+os.sep+"pyacd" 63 | if os.path.exists(pyacd_lib_dir) and os.path.isdir(pyacd_lib_dir): 64 | sys.path.insert(0, pyacd_lib_dir) 65 | 66 | import pyacd 67 | 68 | parser=OptionParser(epilog="This command lists files and directories of " 69 | "your Amazon Cloud Drive. ", 70 | usage="%prog [Options] path1 path2 - ...('-' means STDIN)",version="%prog 0.2") 71 | 72 | parser.add_option("-e","--email",dest="email",action="store",default=None, 73 | help="email address for Amazon.com") 74 | parser.add_option("-p","--password",dest="password",action="store",default=None, 75 | help="password for Amazon.com") 76 | parser.add_option("-s","--session",dest="session",action="store",default=None, 77 | metavar="FILE",help="save or load login session to/from FILE") 78 | parser.add_option("-l",dest="long_format",action="store_true",default=False, 79 | help="use a long listing format") 80 | parser.add_option("-t","--type",dest="list_type",action="store",default="ALL", 81 | metavar="TYPE",help="list type (ALL|FILE|FOLDER) [default: %default]") 82 | parser.add_option("-v","--verbose",dest="verbose",action="store_true",default=False, 83 | help="show debug infomation") 84 | parser.add_option("-q","--quiet",dest="quiet",action="store_true",default=False, 85 | help="quiet mode") 86 | 87 | def main(): 88 | opts,args=parser.parse_args(sys.argv[1:]) 89 | 90 | # Check options of authentication 91 | if opts.session and os.path.exists(opts.session) and not os.path.isdir(opts.session): 92 | pass 93 | elif not opts.email or not opts.password: 94 | sys.stderr.write("!! email and password are required !!\n") 95 | parser.print_help() 96 | sys.exit(2) 97 | 98 | args=list(set(args)) 99 | if "-" in args: 100 | args.remove("-") 101 | args += [x.strip() for x in sys.stdin.readlines()] 102 | 103 | if 0==len(args): 104 | args.append("/") 105 | else: 106 | pass 107 | 108 | # Login to Amazon.com 109 | session=None 110 | try: 111 | if not opts.quiet: 112 | sys.stderr.write("Logining to Amazon.com ... ") 113 | if opts.email and opts.password: 114 | session=pyacd.login(opts.email,opts.password) 115 | else: 116 | fp=open(opts.session,"rb") 117 | session=pickle.load(fp) 118 | fp.close() 119 | session=pyacd.login(session=session) 120 | except: 121 | pass 122 | 123 | # Check login status 124 | if not session: 125 | sys.stderr.write("Unexpected error occured.\n") 126 | sys.exit(2) 127 | elif not session.is_valid(): 128 | sys.stderr.write("Session is invalid.\n%s\n"%session) 129 | sys.exit(2) 130 | elif not session.is_logged_in(): 131 | sys.stderr.write("Login failed.\n%s\n"%session) 132 | sys.exit(2) 133 | 134 | if not opts.quiet: 135 | sys.stderr.write("Done\n") 136 | 137 | if opts.session: 138 | if not opts.quiet: 139 | sys.stderr.write("Updating %s ... "%opts.session) 140 | fp=open(opts.session,"wb") 141 | fp.truncate() 142 | pickle.dump(session,fp) 143 | fp.close() 144 | if not opts.quiet: 145 | sys.stderr.write("Done\n") 146 | 147 | for path in args: 148 | if path[0]!='/':path='/'+path 149 | 150 | if not opts.quiet: 151 | sys.stderr.write("Listing %s ... "%(path)) 152 | 153 | # get path 154 | if opts.verbose: 155 | sys.stderr.write("get ") 156 | try: 157 | pathobj = pyacd.api.get_info_by_path(path) 158 | except pyacd.PyAmazonCloudDriveApiException,e: 159 | sys.stderr.write("Aborted. ('%s')\n"%e.message) 160 | continue 161 | if opts.verbose: 162 | sys.stderr.write("-> ") 163 | 164 | obj=[] 165 | if pathobj.Type== pyacd.types.FILE: 166 | obj.append(pathobj) 167 | else: 168 | if opts.verbose: 169 | sys.stderr.write("info ") 170 | info = pyacd.api.list_by_id(pathobj.object_id) 171 | if opts.verbose: 172 | sys.stderr.write("-> ") 173 | obj+=info.objects 174 | 175 | if not opts.quiet: 176 | sys.stderr.write("Done\n") 177 | 178 | #print obj 179 | if opts.long_format: 180 | print "total %s (%s)"%(len(obj),path) 181 | print "==modified========== ==size/type== ==version== ==name==========" 182 | for o in obj: 183 | if opts.list_type!="ALL" and opts.list_type!=o.Type: 184 | continue 185 | if opts.long_format: 186 | print "%s "%datetime.datetime(*time.localtime(o.modified)[:-3]).isoformat(), 187 | if o.Type == pyacd.types.FILE: 188 | print "%13s"%(o.size if o.size else -1), 189 | else: 190 | print "%13s"%("<"+o.Type+">"), 191 | 192 | print "%11s"%("("+str(o.version)+")"), 193 | 194 | print o.name if o.Type == pyacd.types.FILE else o.name+"/" 195 | 196 | continue 197 | 198 | if __name__=="__main__": 199 | main() 200 | -------------------------------------------------------------------------------- /bin/acdput: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, dis- 9 | # tribute, sublicense, and/or sell copies of the Software, and to permit 10 | # persons to whom the Software is furnished to do so, subject to the fol- 11 | # lowing conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 18 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | # IN THE SOFTWARE. 23 | # 24 | # The Software shall be used for Younger than you, not Older. 25 | # 26 | """ 27 | administrator@Tualatin ~/svn/pyacd $ ./acdput.py --help 28 | Usage: acdput.py [Options] file1 file2 - ...('-' means STDIN) 29 | 30 | Options: 31 | --version show program's version number and exit 32 | -h, --help show this help message and exit 33 | -e EMAIL, --email=EMAIL 34 | email address for Amazon.com 35 | -p PASSWORD, --password=PASSWORD 36 | password for Amazon.com 37 | -s FILE, --session=FILE 38 | save or load login session to/from FILE 39 | -d PATH, --destination=PATH 40 | upload path [default: /] 41 | -v, --verbose show debug infomation 42 | -q, --quiet quiet mode 43 | 44 | This command uploads file(s) to your Amazon Cloud Drive. If there is same 45 | named file, uploading file is renamed automatically. (e.g. 'test.mp3' -> 'test 46 | (2).mp3') 47 | 48 | administrator@Tualatin ~/svn/pyacd $ ./acdput.py -e someone@example.com -p xxxx ~/test.jpg 49 | Logining to Amazon.com ... Done 50 | Uploading test.jpg to / ... Done 51 | """ 52 | 53 | import sys 54 | import os 55 | from optparse import OptionParser 56 | import pickle 57 | 58 | pyacd_lib_dir=os.path.dirname(os.__file__)+os.sep+"pyacd" 59 | if os.path.exists(pyacd_lib_dir) and os.path.isdir(pyacd_lib_dir): 60 | sys.path.insert(0, pyacd_lib_dir) 61 | 62 | import pyacd 63 | 64 | parser=OptionParser(epilog="This command uploads file(s) to your Amazon Cloud Drive. "+ 65 | "If there is same named file, uploading file is renamed "+ 66 | "automatically. (e.g. 'test.mp3' -> 'test (2).mp3')", 67 | usage="%prog [Options] file1 file2 - ...('-' means STDIN)",version="%prog 0.2.1") 68 | 69 | parser.add_option("-e","--email",dest="email",action="store",default=None, 70 | help="email address for Amazon.com") 71 | parser.add_option("-p","--password",dest="password",action="store",default=None, 72 | help="password for Amazon.com") 73 | parser.add_option("-s","--session",dest="session",action="store",default=None, 74 | metavar="FILE",help="save or load login session to/from FILE") 75 | parser.add_option("-d","--destination",dest="path",action="store",default="/", 76 | help="upload path [default: %default]") 77 | parser.add_option("-v","--verbose",dest="verbose",action="store_true",default=False, 78 | help="show debug infomation") 79 | parser.add_option("-q","--quiet",dest="quiet",action="store_true",default=False, 80 | help="quiet mode") 81 | 82 | def main(): 83 | opts,args=parser.parse_args(sys.argv[1:]) 84 | 85 | # Check options of authentication 86 | if opts.session and os.path.exists(opts.session) and not os.path.isdir(opts.session): 87 | pass 88 | elif not opts.email or not opts.password: 89 | sys.stderr.write("!! email and password are required !!\n") 90 | parser.print_help() 91 | sys.exit(2) 92 | 93 | args=list(set(args)) 94 | if "-" in args: 95 | args.remove("-") 96 | args += [x.strip() for x in sys.stdin.readlines()] 97 | 98 | if 0==len(args): 99 | sys.stderr.write("!! no file selected !!\n") 100 | parser.print_help() 101 | sys.exit(2) 102 | else: 103 | for file in args: 104 | if not os.path.exists(file): 105 | sys.stderr.write('Not found "%s"\n'%file) 106 | sys.exit(2) 107 | elif os.path.isdir(file): 108 | sys.stderr.write('"%s" is not file\n'%file) 109 | sys.exit(2) 110 | 111 | # Login to Amazon.com 112 | session=None 113 | try: 114 | if not opts.quiet: 115 | sys.stderr.write("Logining to Amazon.com ... ") 116 | if opts.email and opts.password: 117 | session=pyacd.login(opts.email,opts.password) 118 | else: 119 | fp=open(opts.session,"rb") 120 | session=pickle.load(fp) 121 | fp.close() 122 | session=pyacd.login(session=session) 123 | except: 124 | pass 125 | 126 | # Check login status 127 | if not session: 128 | sys.stderr.write("Unexpected error occured.\n") 129 | sys.exit(2) 130 | elif not session.is_valid(): 131 | sys.stderr.write("Session is invalid.\n%s\n"%session) 132 | sys.exit(2) 133 | elif not session.is_logged_in(): 134 | sys.stderr.write("Login failed.\n%s\n"%session) 135 | sys.exit(2) 136 | 137 | if not opts.quiet: 138 | sys.stderr.write("Done\n") 139 | 140 | if opts.session: 141 | if not opts.quiet: 142 | sys.stderr.write("Updating %s ... "%opts.session) 143 | fp=open(opts.session,"wb") 144 | fp.truncate() 145 | pickle.dump(session,fp) 146 | fp.close() 147 | if not opts.quiet: 148 | sys.stderr.write("Done\n") 149 | 150 | # Check destination 151 | path=opts.path 152 | if path[0]!='/':path='/'+path 153 | if path[-1]!='/':path=path+'/' 154 | try: 155 | dest = pyacd.api.get_info_by_path(path) 156 | if dest.Type == pyacd.types.FILE: 157 | sys.stderr.write('"%s" is file\n'%path) 158 | sys.exit(2) 159 | except pyacd.PyAmazonCloudDriveApiException,e: 160 | sys.stderr.write('"%s"\n'%e.message) 161 | sys.exit(2) 162 | 163 | 164 | for file in args: 165 | filename = os.path.basename(file) 166 | f=open(file,"rb") 167 | filedata = f.read() 168 | f.close() 169 | 170 | if not opts.quiet: 171 | sys.stderr.write("Uploading %s to %s ... "%(filename,path)) 172 | 173 | # create file 174 | if opts.verbose: 175 | sys.stderr.write("create ") 176 | fileobj = pyacd.api.create_by_path(path,filename) 177 | if opts.verbose: 178 | sys.stderr.write("-> ") 179 | 180 | # get upload_url 181 | if opts.verbose: 182 | sys.stderr.write("url ") 183 | upload_url = pyacd.api.get_upload_url_by_id(fileobj.object_id,len(filedata)) 184 | if opts.verbose: 185 | sys.stderr.write("-> ") 186 | 187 | end_point=upload_url.http_request.end_point 188 | parameters=upload_url.http_request.parameters 189 | 190 | storage_key=upload_url.storage_key 191 | object_id=upload_url.object_id 192 | 193 | # upload file 194 | if opts.verbose: 195 | sys.stderr.write("send ") 196 | pyacd.api.upload(end_point,parameters,filename,filedata) 197 | if opts.verbose: 198 | sys.stderr.write("-> ") 199 | 200 | # completeing file 201 | if opts.verbose: 202 | sys.stderr.write("finish ") 203 | pyacd.api.complete_file_upload_by_id(object_id,storage_key) 204 | if opts.verbose: 205 | sys.stderr.write("-> ") 206 | 207 | if not opts.quiet: 208 | sys.stderr.write("Done\n") 209 | 210 | 211 | if __name__=="__main__": 212 | main() 213 | -------------------------------------------------------------------------------- /pyacd/api.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 anatanokeitai.com(sakurai_youhei) 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, dis- 7 | # tribute, sublicense, and/or sell copies of the Software, and to permit 8 | # persons to whom the Software is furnished to do so, subject to the fol- 9 | # lowing conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16 | # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17 | # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | # 22 | # The Software shall be used for Younger than you, not Older. 23 | # 24 | """API wrappers 25 | 26 | * can_device_download: Check whether downloading is allowed. 27 | * complete_file_upload_by_id: Finalize uploading file. 28 | * copy_bulk_by_id: Copy file"s" or folder"s". 29 | * create_by_id: Create file or folder into somewhere(specified id). 30 | * create_by_path: Create file or folder into somewhere(specified absolute path). 31 | * download_by_id: Download file after can_device_download 32 | * empty_recycle_bin: Empty out "/RecycleBin". 33 | * get_info_by_id: Get informations of file or folder(specified id). 34 | * get_info_by_path: Get informations of file or folder(specified absolute path). 35 | * get_subscription_problem: Get information of xxx(I don't know) 36 | * get_upload_url_by_id: Get uploading URL to S3 after create_by_xxx. 37 | * get_user_storage: Get informations of user storage. 38 | * list_by_id: List entities in somewhere. 39 | * move_bulk_by_id: Move file"s" or folder"s" to to somewhere. 40 | * move_by_id: Move file or folder to to somewhere. 41 | * recycle_bulk_by_id: Move file"s" or folder"s" to to "/RecycleBin". 42 | * remove_bulk_by_id: Remove file"s" or folder"s". 43 | * select_metadata: Query metadata like SQL. 44 | * upload: Upload file's data to S3. (There is NOT such api but utility function) 45 | 46 | Naming rules is following. 47 | +-------------------------+----------------------------+ 48 | |pyacd.api's function name|Cloud Drive's operation name| 49 | +-------------------------+----------------------------+ 50 | |snake case |lower camel case | 51 | |(e.g. get_info_by_id) |(e.g. getInfoById) | 52 | +-------------------------+----------------------------+ 53 | 54 | +-------------------------+----------------------------+ 55 | |pyacd.api's key name |Cloud Drive's key name | 56 | +-------------------------+----------------------------+ 57 | |snake case |lower camel case | 58 | |(e.g. object_id) |(e.g. objectId) | 59 | +------------------------------------------------------+ 60 | | [Exceptions] | 61 | +-------------------------+----------------------------+ 62 | |Type |type | 63 | |modified |lastUpdatedDate | 64 | |created |creationDate | 65 | +------------------------------------------------------+ 66 | """ 67 | 68 | import sys 69 | import time 70 | import urllib 71 | try: 72 | import json 73 | except ImportError,e: 74 | import simplejson as json 75 | 76 | import pyacd 77 | from .apiresponse import * 78 | 79 | 80 | def _error_check(resp_json): 81 | if resp_json.get("Error"): 82 | raise pyacd.PyAmazonCloudDriveApiException(resp_json.get("Error")) 83 | 84 | def upload(end_point,parameters,filename,filedata): 85 | """Upload file's data to S3. (There is NOT such api but utility function) 86 | 87 | :type end_point: string 88 | :param end_point: uploading URL of S3 89 | 90 | :type parameters: dict 91 | :param parameters: uploading params of S3 92 | 93 | :type filename: string 94 | :param filename: file name stored in Amazon Cloud Drive 95 | 96 | :type filedata: binary 97 | :param filedata: like open("/path/to/file","rb").read() 98 | """ 99 | params=parameters.copy() 100 | params["Filename"]=filename 101 | pyacd.post_multipart(end_point,params,{filename:filedata}) 102 | 103 | def complete_file_upload_by_id(object_id,storage_key): 104 | """Finalize uploading file. 105 | 106 | :type object_id: string 107 | :param object_id: upload_url.object_id 108 | 109 | :type storage_key: string 110 | :param storage_key: upload_url.storage_key 111 | """ 112 | session = pyacd.get_session() 113 | if not session.is_logged_in(): 114 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 115 | 116 | operation="completeFileUploadById" 117 | params={ 118 | "_":int(time.time()), 119 | "Operation":operation, 120 | "customerId":session.customer_id, 121 | "ContentType":"JSON", 122 | "objectId":object_id, 123 | "storageKey":storage_key, 124 | } 125 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 126 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 127 | _error_check(resp_json) 128 | 129 | def can_device_download(): 130 | """Check whether downloading is allowed. 131 | 132 | :rtype: bool 133 | :return: whether downloading is allowed. 134 | """ 135 | session = pyacd.get_session() 136 | if not session.is_logged_in(): 137 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 138 | 139 | operation="canDeviceDownload" 140 | params={ 141 | "_":int(time.time()), 142 | "Operation":operation, 143 | "customerId":session.customer_id, 144 | "ContentType":"JSON", 145 | "deviceId.deviceType":"ubid", 146 | "deviceId.deviceSerialNumber":session.cookies["ubid-main"] 147 | } 148 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 149 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 150 | _error_check(resp_json) 151 | 152 | result=resp_json.get(operation+"Response").get(operation+"Result") 153 | return result["canDownload"] 154 | 155 | def get_upload_url_by_id(object_id,size,method="POST"): 156 | """Get uploading URL to S3 after create_by_xxx. 157 | 158 | :type object_id: string 159 | :param object_id: 160 | 161 | :type size: int 162 | :param size: like len(data) 163 | 164 | :type method: string 165 | :param method: I know only "POST". 166 | 167 | :rtype: :class:`pyacd.apiresponse.UploadUrl` 168 | :return: informations of uploading. 169 | """ 170 | session = pyacd.get_session() 171 | if not session.is_logged_in(): 172 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 173 | 174 | operation="getUploadUrlById" 175 | params={ 176 | "_":int(time.time()), 177 | "Operation":operation, 178 | "customerId":session.customer_id, 179 | "ContentType":"JSON", 180 | "objectId":object_id, 181 | "size":size, 182 | "method":method 183 | } 184 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 185 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 186 | _error_check(resp_json) 187 | 188 | result=resp_json.get(operation+"Response").get(operation+"Result") 189 | return UploadUrl(result) 190 | 191 | def download_by_id(object_id,attachment=0): 192 | """Download file after can_device_download. 193 | 194 | :type object_id: string 195 | :param object_id: 196 | 197 | :type attachment: (?)int 198 | :param attachment: (?)header of "Content-disposition: attachment" 199 | 200 | :rtype: binary 201 | :return: data stored in S3 202 | """ 203 | session = pyacd.get_session() 204 | if not session.is_logged_in(): 205 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 206 | 207 | if not can_device_download(): 208 | sys.stderr.write( 209 | "\n\n"+ 210 | "You have exceeded the maximum number of devices allowed. "+ 211 | "Downloading is disabled for this browser.\n\n"+ 212 | "SEE ALSO http://www.amazon.com/gp/help/customer/display.html/?ie=UTF8&nodeId=200557340\n"+ 213 | "Frequently Asked Questions\n"+ 214 | "How many devices can I use to access the files I've stored in my Cloud Drive?\n"+ 215 | "\n\n" 216 | ) 217 | raise pyacd.PyAmazonCloudDriveError("device limit (up to eight devices.) can be reached.") 218 | 219 | params={ 220 | "downloadById":object_id, 221 | "attachment":attachment 222 | } 223 | end_point=pyacd.api_root[:-1*len("/api/")]+"?"+urllib.urlencode(params) 224 | #print end_point 225 | return pyacd.conn.do_get(end_point) 226 | 227 | def empty_recycle_bin(): 228 | """Empty out "/RecycleBin". 229 | """ 230 | session = pyacd.get_session() 231 | if not session.is_logged_in(): 232 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 233 | 234 | operation="emptyRecycleBin" 235 | params={ 236 | "_":int(time.time()), 237 | "Operation":operation, 238 | "customerId":session.customer_id, 239 | "ContentType":"JSON", 240 | } 241 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 242 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 243 | _error_check(resp_json) 244 | 245 | def recycle_bulk_by_id(source_inclusion_ids=[]): 246 | """Move file"s" or folder"s" to to "/RecycleBin". 247 | 248 | :type source_inclusion_ids: list 249 | :param source_inclusion_ids: list of object_id 250 | """ 251 | _operate2_bulk_by_id("recycleBulkById",source_inclusion_ids) 252 | 253 | def remove_bulk_by_id(source_inclusion_ids=[]): 254 | """Remove file"s" or folder"s". 255 | 256 | :type source_inclusion_ids: list 257 | :param source_inclusion_ids: list of object_id 258 | """ 259 | _operate2_bulk_by_id("removeBulkById",source_inclusion_ids) 260 | 261 | def _operate2_bulk_by_id(operation,source_inclusion_ids): 262 | session = pyacd.get_session() 263 | if not session.is_logged_in(): 264 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 265 | 266 | if len(source_inclusion_ids)==0: 267 | raise pyacd.PyAmazonCloudDriveError("No source ids %s"%str(source_inclusion_ids)) 268 | 269 | params={ 270 | "_":int(time.time()), 271 | "Operation":operation, 272 | "customerId":session.customer_id, 273 | "ContentType":"JSON", 274 | } 275 | params.update(dict([["inclusionIds.member.%d"%(i+1),source_inclusion_ids[i]] for i in range(len(source_inclusion_ids))])) 276 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 277 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 278 | _error_check(resp_json) 279 | 280 | def _operate1_bulk_by_id(operation,destination_parent_id,source_inclusion_ids,conflict_resolution): 281 | session = pyacd.get_session() 282 | if not session.is_logged_in(): 283 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 284 | 285 | if len(source_inclusion_ids)==0: 286 | raise pyacd.PyAmazonCloudDriveError("No source ids %s"%str(source_inclusion_ids)) 287 | 288 | params={ 289 | "_":int(time.time()), 290 | "Operation":operation, 291 | "customerId":session.customer_id, 292 | "ContentType":"JSON", 293 | "destinationParentId":destination_parent_id, 294 | "conflictResolution":conflict_resolution, 295 | } 296 | params.update(dict([["sourceInclusionIds.member.%d"%(i+1),source_inclusion_ids[i]] for i in range(len(source_inclusion_ids))])) 297 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 298 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 299 | _error_check(resp_json) 300 | 301 | def move_bulk_by_id(destination_parent_id,source_inclusion_ids=[],conflict_resolution="MERGE"): 302 | """Move file"s" or folder"s" to to somewhere. 303 | 304 | :type destination_parent_id: string 305 | :param destination_parent_id: folder's object_id 306 | 307 | :type source_inclusion_ids: list 308 | :param source_inclusion_ids: list of object_id 309 | 310 | :type conflict_resolution: string 311 | :param conflict_resolution: "RENAME" | "MERGE" 312 | """ 313 | _operate1_bulk_by_id("moveBulkById",destination_parent_id,source_inclusion_ids,"MERGE") 314 | 315 | def copy_bulk_by_id(destination_parent_id,source_inclusion_ids=[],conflict_resolution="RENAME"): 316 | """Copy file"s" or folder"s". 317 | :type destination_parent_id: string 318 | :param destination_parent_id: folder's object_id 319 | 320 | :type source_inclusion_ids: list 321 | :param source_inclusion_ids: list of object_id 322 | 323 | :type conflict_resolution: string 324 | :param conflict_resolution: "RENAME" | "MERGE" 325 | """ 326 | _operate1_bulk_by_id("copyBulkById",destination_parent_id,source_inclusion_ids,"RENAME") 327 | 328 | def move_by_id(source_id,destination_parent_id,destination_name,overwrite=False): 329 | """Move file or folder to to somewhere. 330 | 331 | :type source_id: string 332 | :param source_id: 333 | 334 | :type destination_parent_id: string 335 | :param destination_parent_id: 336 | 337 | :type destination_name: new name 338 | :param destination_name: 339 | 340 | :type overwrite: bool 341 | :param overwrite: whether override or not. 342 | """ 343 | session = pyacd.get_session() 344 | if not session.is_logged_in(): 345 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 346 | 347 | operation="moveById" 348 | params={ 349 | "_":int(time.time()), 350 | "Operation":operation, 351 | "customerId":session.customer_id, 352 | "ContentType":"JSON", 353 | "sourceId":source_id, 354 | "destinationParentId":destination_parent_id, 355 | "destinationName":destination_name, 356 | "overwrite":"true" if overwrite else "false" 357 | } 358 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 359 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 360 | _error_check(resp_json) 361 | 362 | 363 | def create_by_path(path,name,Type=pyacd.types.FILE,conflict_resolution="RENAME",overwrite=False,autoparent=True): 364 | """Create file or folder into somewhere(specified absolute path). 365 | 366 | :type path: string 367 | :param path: 368 | 369 | :type name: string 370 | :param name: 371 | 372 | :type Type: string 373 | :param Type: pyacd.types.FILE | pyacd.types.FOLDER 374 | 375 | :type conflict_resolution: string 376 | :param conflict_resolution: "RENAME" | "MERGE" 377 | 378 | :type overwrite: bool 379 | :param overwrite: whether override or not. 380 | 381 | :type autoparent: bool 382 | :param autoparent: (?) 383 | 384 | :rtype: :class:`pyacd.apiresponse.Info` 385 | :return: information of created one. 386 | """ 387 | session = pyacd.get_session() 388 | if not session.is_logged_in(): 389 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 390 | 391 | operation="createByPath" 392 | params={ 393 | "_":int(time.time()), 394 | "Operation":operation, 395 | "customerId":session.customer_id, 396 | "ContentType":"JSON", 397 | "path":path, 398 | "name":name, 399 | "type":Type, 400 | "conflictResolution":conflict_resolution, 401 | "overwrite":"true" if overwrite else "false", 402 | "autoparent":"true" if autoparent else "false" 403 | } 404 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 405 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 406 | _error_check(resp_json) 407 | result=resp_json.get(operation+"Response").get(operation+"Result") 408 | return Info(result.get("info")) 409 | 410 | def create_by_id(parent_id,name,Type=pyacd.types.FOLDER,overwrite=False): 411 | """Create file or folder into somewhere(specified id). 412 | 413 | :type parent_id: string 414 | :param parent_id: 415 | 416 | :type name: string 417 | :param name: 418 | 419 | :type Type: string 420 | :param Type: pyacd.types.FILE | pyacd.types.FOLDER 421 | 422 | :type overwrite: bool 423 | :param overwrite: whether override or not. 424 | 425 | :rtype: :class:`pyacd.apiresponse.Info` 426 | :return: information of created one. 427 | """ 428 | session = pyacd.get_session() 429 | if not session.is_logged_in(): 430 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 431 | 432 | operation="createById" 433 | params={ 434 | "_":int(time.time()), 435 | "Operation":operation, 436 | "customerId":session.customer_id, 437 | "ContentType":"JSON", 438 | "parentId":parent_id, 439 | "name":name, 440 | "type":Type, 441 | "overwrite":"true" if overwrite else "false" 442 | } 443 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 444 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 445 | _error_check(resp_json) 446 | result=resp_json.get(operation+"Response").get(operation+"Result") 447 | return Info(result.get("info")) 448 | 449 | 450 | def list_by_id(object_id,ordering=None,next_token=0,max_items=None,Filter=None): 451 | """List entities in somewhere. 452 | 453 | :type object_id: string 454 | :param object_id: 455 | 456 | :type ordering: string 457 | :param ordering: comma separated and like "order by" in SQL 458 | e.g. keyName 459 | type,keyName,creationDate 460 | 461 | :type next_token: (?)int 462 | :param next_token: (?) 463 | 464 | :type max_items: int 465 | :param max_items: None means unlimited 466 | 467 | :type Filter: string 468 | :param Filter: like "where" in SQL 469 | e.g. type = "FOLDER" and hidden = false 470 | type != "RECYCLE" and status != "PENDING" and hidden = false 471 | 472 | :rtype: :class:`pyacd.apiresponse.List` 473 | :return: informations listed. 474 | """ 475 | session = pyacd.get_session() 476 | if not session.is_logged_in(): 477 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 478 | 479 | operation="listById" 480 | params={ 481 | "_":int(time.time()), 482 | "Operation":operation, 483 | "customerId":session.customer_id, 484 | "ContentType":"JSON", 485 | "objectId":object_id, 486 | "nextToken":next_token 487 | } 488 | if ordering:params["ordering"]=ordering 489 | if max_items:params["maxItems"]=max_items 490 | if Filter:params["filter"]=Filter 491 | 492 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 493 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 494 | _error_check(resp_json) 495 | result=resp_json.get(operation+"Response").get(operation+"Result") 496 | return List(result) 497 | 498 | 499 | def select_metadata(query): 500 | """Query metadata like SQL. 501 | 502 | :type query: string 503 | :param query: like SQL 504 | e.g. select count(*) from object where hidden != true and parentObjectId='xxx' and status != 'PENDING' and type != 'RECYCLE' and type = "FOLDER" 505 | select count(*) from object where hidden != true and parentObjectId='xxx' and status != 'PENDING' and type != 'RECYCLE' 506 | select distinct parentObjectId from object where parent.parentObjectId='xxx' and type != "RECYCLE" and hidden = false and status != "PENDING" 507 | select distinct parentObjectId from object where parent.parentObjectId='xxx' and type = 'FOLDER' and hidden = false 508 | select creationDate, extension, objectId, keyName, purchaseDate, parentObjectId, status, name, lastModifiedDate, version, type, size, parent.name from object where purchaseDate = null and type='FILE' and hidden=false and status='AVAILABLE' order by creationDate DESC,keyName limit 0, 2 509 | select creationDate, extension, objectId, keyName, purchaseDate, parentObjectId, status, name, lastModifiedDate, version, type, size, parent.name from object where type='FILE' and hidden=false and status='AVAILABLE' order by creationDate DESC,keyName limit 0, 2 510 | 511 | :rtype: :class:`pyacd.apiresponse.Metadata` 512 | :return: informations selected. 513 | """ 514 | session = pyacd.get_session() 515 | if not session.is_logged_in(): 516 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 517 | 518 | operation="selectMetadata" 519 | params={ 520 | "_":int(time.time()), 521 | "Operation":operation, 522 | "customerId":session.customer_id, 523 | "ContentType":"JSON", 524 | "query":query 525 | } 526 | 527 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 528 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 529 | _error_check(resp_json) 530 | result=resp_json.get(operation+"Response").get(operation+"Result") 531 | return Metadata(result) 532 | 533 | def get_info_by_path(path): 534 | """Get informations of file or folder(specified absolute path). 535 | 536 | :type path: string 537 | :param path: 538 | 539 | :rtype: :class:`pyacd.apiresponse.Info` 540 | :return: information. 541 | """ 542 | session = pyacd.get_session() 543 | if not session.is_logged_in(): 544 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 545 | 546 | operation="getInfoByPath" 547 | params={ 548 | "_":int(time.time()), 549 | "Operation":operation, 550 | "customerId":session.customer_id, 551 | "ContentType":"JSON", 552 | "path":path, 553 | "populatePath":"true" 554 | } 555 | 556 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 557 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 558 | _error_check(resp_json) 559 | result=resp_json.get(operation+"Response").get(operation+"Result") 560 | return Info(result) 561 | 562 | def get_info_by_id(object_id): 563 | """Get informations of file or folder(specified id). 564 | 565 | :type object_id: string 566 | :param object_id: 567 | 568 | :rtype: :class:`pyacd.apiresponse.Info` 569 | :return: information. 570 | """ 571 | session = pyacd.get_session() 572 | if not session.is_logged_in(): 573 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 574 | 575 | operation="getInfoById" 576 | params={ 577 | "_":int(time.time()), 578 | "Operation":operation, 579 | "customerId":session.customer_id, 580 | "ContentType":"JSON", 581 | "objectId":object_id, 582 | "populatePath":"true" 583 | } 584 | 585 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 586 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 587 | _error_check(resp_json) 588 | result=resp_json.get(operation+"Response").get(operation+"Result") 589 | return Info(result) 590 | 591 | 592 | def get_user_storage(): 593 | """Get informations of user storage. 594 | 595 | :rtype: :class:`pyacd.apiresponse.UserStorage` 596 | :return: information. 597 | """ 598 | session = pyacd.get_session() 599 | if not session.is_logged_in(): 600 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 601 | 602 | operation="getUserStorage" 603 | params={ 604 | "_":int(time.time()), 605 | "Operation":operation, 606 | "customerId":session.customer_id, 607 | "ContentType":"JSON" 608 | } 609 | 610 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 611 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 612 | _error_check(resp_json) 613 | result=resp_json.get(operation+"Response").get(operation+"Result") 614 | 615 | return UserStorage(result) 616 | 617 | 618 | 619 | 620 | def get_subscription_problem(): 621 | """Get information of xxx. (I don't know) 622 | 623 | :rtype: :class:`pyacd.apiresponse.SubscriptionProblem` 624 | :return: (?) 625 | """ 626 | session = pyacd.get_session() 627 | if not session.is_logged_in(): 628 | raise pyacd.PyAmazonCloudDriveError("Not logined %s"%session) 629 | 630 | operation="getSubscriptionProblem" 631 | params={ 632 | "_":int(time.time()), 633 | "Operation":operation, 634 | "customerId":session.customer_id, 635 | "ContentType":"JSON" 636 | } 637 | end_point=pyacd.api_root+"?"+urllib.urlencode(params) 638 | resp_json=json.loads(pyacd.conn.do_get(end_point)) 639 | _error_check(resp_json) 640 | result=resp_json.get(operation+"Response").get(operation+"Result") 641 | 642 | return SubscriptionProblem(result) 643 | 644 | 645 | 646 | 647 | 648 | 649 | --------------------------------------------------------------------------------