This commit is contained in:
Yuriy Khomyakov 2017-04-17 00:10:58 +03:00
commit e80fe6c108
18 changed files with 3633 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.pyc
/**/*.pyc

22
LICENSE.md Normal file
View file

@ -0,0 +1,22 @@
COPYRIGHT AND PERMISSION NOTICE
Copyright (c) 2016, The WDC Project, and many
contributors, see the THANKS file.
All rights reserved.
Permission to use, copy, modify, and distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright
notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization of the copyright holder.

410
README.md Normal file
View file

@ -0,0 +1,410 @@
webdavclient
============
[![PyPI
version](https://badge.fury.io/py/webdavclient.svg)](http://badge.fury.io/py/webdavclient)
[![Requirements
Status](https://requires.io/github/designerror/webdav-client-python/requirements.svg?branch=master&style=flat)](https://requires.io/github/designerror/webdav-client-python/requirements/?branch=master&style=flat)
[![PullReview
stats](https://www.pullreview.com/github/designerror/webdavclient/badges/master.svg?)](https://www.pullreview.com/github/designerror/webdavclient/reviews/master)
Package webdavclient provides easy and convenient work with WebDAV-servers (Yandex.Drive, Dropbox, Google Drive, Box, 4shared, etc.). The package includes the following components: webdav API, resource API and wdc.
The source code of the project can be found
[here](https://github.com/designerror/webdavclient)
![Github](https://github.com/favicon.ico)
Installation and upgrade
======================
**Installation**
> Linux
```bash
$ sudo apt-get install libxml2-dev libxslt-dev python-dev
$ sudo apt-get install libcurl4-openssl-dev python-pycurl
$ sudo easy_install webdavclient
```
> macOS
```bash
$ curl https://bootstrap.pypa.io/ez_setup.py -o - | python
$ python setup.py install --prefix=/opt/setuptools
$ sudo easy_install webdavclient
```
**Update**
```bash
$ sudo pip install -U webdavclient
```
Webdav API
==========
Webdav API is a set of webdav methods of work with cloud storage. This set includes the following methods: `check`, `free`, `info`, `list`, `mkdir`, `clean`, `copy`, `move`, `download`, `upload`, `publish` and `unpublish`.
**Configuring the client**
Required keys for configuring client connection with WevDAV-server are webdav\_hostname and webdav\_login, webdav,\_password.
```python
import webdav.client as wc
options = {
'webdav_hostname': "https://webdav.server.ru",
'webdav_login': "login",
'webdav_password': "password"
}
client = wc.Client(options)
```
When a proxy server you need to specify settings to connect through it.
```python
import webdav.client as wc
options = {
'webdav_hostname': "https://webdav.server.ru",
'webdav_login': "w_login",
'webdav_password': "w_password",
'proxy_hostname': "http://127.0.0.1:8080",
'proxy_login': "p_login",
'proxy_password': "p_password"
}
client = wc.Client(options)
```
If you want to use the certificate path to certificate and private key is defined as follows:
```python
import webdav.client as wc
options = {
'webdav_hostname': "https://webdav.server.ru",
'webdav_login': "w_login",
'webdav_password': "w_password",
'cert_path': "/etc/ssl/certs/certificate.crt",
'key_path': "/etc/ssl/private/certificate.key"
}
client = wc.Client(options)
```
Or you want to limit the speed or turn on verbose mode:
```python
options = {
...
'recv_speed' : 3000000,
'send_speed' : 3000000,
'verbose' : True
}
client = wc.Client(options)
```
recv_speed: rate limit data download speed in Bytes per second. Defaults to unlimited speed.
send_speed: rate limit data upload speed in Bytes per second. Defaults to unlimited speed.
verbose: set verbose mode on/off. By default verbose mode is off.
**Synchronous methods**
```python
// Checking existence of the resource
client.check("dir1/file1")
client.check("dir1")
```
```python
// Get information about the resource
client.info("dir1/file1")
client.info("dir1/")
```
```python
// Check free space
free_size = client.free()
```
```python
// Get a list of resources
files1 = client.list()
files2 = client.list("dir1")
```
```python
// Create directory
client.mkdir("dir1/dir2")
```
```python
// Delete resource
client.clean("dir1/dir2")
```
```python
// Copy resource
client.copy(remote_path_from="dir1/file1", remote_path_to="dir2/file1")
client.copy(remote_path_from="dir2", remote_path_to="dir3")
```
```python
// Move resource
client.move(remote_path_from="dir1/file1", remote_path_to="dir2/file1")
client.move(remote_path_from="dir2", remote_path_to="dir3")
```
```python
// Move resource
client.download_sync(remote_path="dir1/file1", local_path="~/Downloads/file1")
client.download_sync(remote_path="dir1/dir2/", local_path="~/Downloads/dir2/")
```
```python
// Unload resource
client.upload_sync(remote_path="dir1/file1", local_path="~/Documents/file1")
client.upload_sync(remote_path="dir1/dir2/", local_path="~/Documents/dir2/")
```
```python
// Publish the resource
link = client.publish("dir1/file1")
link = client.publish("dir2")
```
```python
// Unpublish resource
client.unpublish("dir1/file1")
client.unpublish("dir2")
```
```python
// Exception handling
from webdav.client import WebDavException
try:
...
except WebDavException as exception:
...
```
```python
// Get the missing files
client.pull(remote_directory='dir1', local_directory='~/Documents/dir1')
```
```python
// Send missing files
client.push(remote_directory='dir1', local_directory='~/Documents/dir1')
```
**Asynchronous methods**
```python
// Load resource
kwargs = {
'remote_path': "dir1/file1",
'local_path': "~/Downloads/file1",
'callback': callback
}
client.download_async(**kwargs)
kwargs = {
'remote_path': "dir1/dir2/",
'local_path': "~/Downloads/dir2/",
'callback': callback
}
client.download_async(**kwargs)
```
```python
// Unload resource
kwargs = {
'remote_path': "dir1/file1",
'local_path': "~/Downloads/file1",
'callback': callback
}
client.upload_async(**kwargs)
kwargs = {
'remote_path': "dir1/dir2/",
'local_path': "~/Downloads/dir2/",
'callback': callback
}
client.upload_async(**kwargs)
```
Resource API
============
Resource API using the concept of OOP that enables cloud-level resources.
```python
// Get a resource
res1 = client.resource("dir1/file1")
```
```python
// Work with the resource
res1.rename("file2")
res1.move("dir1/file2")
res1.copy("dir2/file1")
info = res1.info()
res1.read_from(buffer)
res1.read(local_path="~/Documents/file1")
res1.read_async(local_path="~/Documents/file1", callback)
res1.write_to(buffer)
res1.write(local_path="~/Downloads/file1")
res1.write_async(local_path="~/Downloads/file1", callback)
```
wdc
===
wdc - a cross-platform utility that provides convenient work with WebDAV-servers right from your console. In addition to full implementations of methods from webdav API, also added methods content sync local and remote directories.
**Authentication**
- *Basic authentication*
```bash
$ wdc login https://wedbav.server.ru -p http://127.0.0.1:8080
webdav_login: w_login
webdav_password: w_password
proxy_login: p_login
proxy_password: p_password
success
```
- Authorize the application using OAuth token*
```bash
$ wdc login https://wedbav.server.ru -p http://127.0.0.1:8080 --token xxxxxxxxxxxxxxxxxx
proxy_login: p_login
proxy_password: p_password
success
```
There are also additional keys `--root[-r]`, `--cert-path[-c]` and `--key-path[-k]`.
**Utility**
```bash
$ wdc check
success
$ wdc check file1
not success
$ wdc free
245234120344
$ wdc ls dir1
file1
...
fileN
$ wdc mkdir dir2
$ wdc copy dir1/file1 -t dir2/file1
$ wdc move dir2/file1 -t dir2/file2
$ wdc download dir1/file1 -t ~/Downloads/file1
$ wdc download dir1/ -t ~/Downloads/dir1/
$ wdc upload dir2/file2 -f ~/Documents/file1
$ wdc upload dir2/ -f ~/Documents/
$ wdc publish di2/file2
https://yadi.sk/i/vWtTUcBucAc6k
$ wdc unpublish dir2/file2
$ wdc pull dir1/ -t ~/Documents/dir1/
$ wdc push dir1/ -f ~/Documents/dir1/
$ wdc info dir1/file1
{'name': 'file1', 'modified': 'Thu, 23 Oct 2014 16:16:37 GMT',
'size': '3460064', 'created': '2014-10-23T16:16:37Z'}
```
WebDAV-server
=============
The most popular cloud-based repositories that support the Protocol WebDAV can be attributed Yandex.Drive, Dropbox, Google Drive, Box and 4shared. Access to data repositories, operating with access to the Internet. If necessary local locations and cloud storage, you can deploy your own WebDAV-server.
**Local WebDAV-server**
To deploy a local WebDAV server, using Docker containers
quite easily and quickly. To see an example of a local deploymentWebDAV servers can be on the project [webdav-server-docker](https://github.com/designerror/webdav-server-docker).
**Supported methods**
Servers |free|info|list|mkdir|clean|copy|move|download|upload
:------------|:--:|:--:|:--:|:---:|:---:|:--:|:--:|:------:|:----:
Yandex.Disk| \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+
Dropbox| \- | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+
Google Drive| \- | \+ | \+ | \+ | \+ | \- | \- | \+ | \+
Box| \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+
4shared| \- | \+ | \+ | \+ | \+ | \- | \- | \+ | \+
Webdavserver| \- | \+ | \+ | \+ | \+ | \- | \- | \+ | \+
Publish and unpublish methods supports only Yandex.Disk.
**Configuring connections**
To work with cloud storage Dropbox and Google Drive via the WebDAV Protocol, you must use a WebDAV-server DropDAV and DAV-pocket, respectively.
A list of settings for WebDAV servers:
```yaml
webdav-servers:
- yandex
hostname: https://webdav.yandex.ru
login: #login_for_yandex
password: #pass_for_yandex
- dropbox
hostname: https://dav.dropdav.com
login: #login_for dropdav
password: #pass_for_dropdav
- google
hostname: https://dav-pocket.appspot.com
root: docso
login: #login_for_dav-pocket
password: #pass_for_dav-pocket
- box
hostname: https://dav.box.com
root: dav
login: #login_for_box
password: #pass_for_box
- 4shared
hostname: https://webdav.4shared.com
login: #login_for_4shared
password: #pass_for_4shared
```
Autocompletion
==============
For macOS, or older Unix systems you need to update bash.
```bash
$ brew install bash
$ chsh
$ brew install bash-completion
```
Autocompletion can be enabled globally
```bash
$ sudo activate-global-python-argcomplete
```
or locally
```bash
#.bashrc
eval "$(register-python-argcomplete wdc)"
```

446
README.rst Normal file
View file

@ -0,0 +1,446 @@
webdavclient
============
|PyPI version| |Requirements Status| |PullReview stats|
Package webdavclient provides easy and convenient work with
WebDAV-servers (Yandex.Drive, Dropbox, Google Drive, Box, 4shared,
etc.). The package includes the following components: webdav API,
resource API and wdc.
The source code of the project can be found
`here <https://github.com/designerror/webdavclient>`__ |Github|
Installation and upgrade
========================
**Installation**
- Linux
.. code:: bash
$ sudo apt-get install libxml2-dev libxslt-dev python-dev
$ sudo apt-get install libcurl4-openssl-dev python-pycurl
$ sudo easy_install webdavclient
- macOS
.. code:: bash
$ curl https://bootstrap.pypa.io/ez_setup.py -o - | python
$ python setup.py install --prefix=/opt/setuptools
$ sudo easy_install webdavclient
**Update**
.. code:: bash
$ sudo pip install -U webdavclient
Webdav API
==========
Webdav API is a set of webdav methods of work with cloud storage. This
set includes the following methods: ``check``, ``free``, ``info``,
``list``, ``mkdir``, ``clean``, ``copy``, ``move``, ``download``,
``upload``, ``publish`` and ``unpublish``.
**Configuring the client**
Required keys for configuring client connection with WevDAV-server are
webdav\_hostname and webdav\_login, webdav,\_password.
.. code:: python
import webdav.client as wc
options = {
'webdav_hostname': "https://webdav.server.ru",
'webdav_login': "login",
'webdav_password': "password"
}
client = wc.Client(options)
When a proxy server you need to specify settings to connect through it.
.. code:: python
import webdav.client as wc
options = {
'webdav_hostname': "https://webdav.server.ru",
'webdav_login': "w_login",
'webdav_password': "w_password",
'proxy_hostname': "http://127.0.0.1:8080",
'proxy_login': "p_login",
'proxy_password': "p_password"
}
client = wc.Client(options)
If you want to use the certificate path to certificate and private key
is defined as follows:
.. code:: python
import webdav.client as wc
options = {
'webdav_hostname': "https://webdav.server.ru",
'webdav_login': "w_login",
'webdav_password': "w_password",
'cert_path': "/etc/ssl/certs/certificate.crt",
'key_path': "/etc/ssl/private/certificate.key"
}
client = wc.Client(options)
Or you want to limit the speed or turn on verbose mode:
.. code:: python
options = {
...
'recv_speed' : 3000000,
'send_speed' : 3000000,
'verbose' : True
}
client = wc.Client(options)
| recv\_speed: rate limit data download speed in Bytes per second.
Defaults to unlimited speed.
| send\_speed: rate limit data upload speed in Bytes per second.
Defaults to unlimited speed.
| verbose: set verbose mode on/off. By default verbose mode is off.
**Synchronous methods**
.. code:: python
// Checking existence of the resource
client.check("dir1/file1")
client.check("dir1")
.. code:: python
// Get information about the resource
client.info("dir1/file1")
client.info("dir1/")
.. code:: python
// Check free space
free_size = client.free()
.. code:: python
// Get a list of resources
files1 = client.list()
files2 = client.list("dir1")
.. code:: python
// Create directory
client.mkdir("dir1/dir2")
.. code:: python
// Delete resource
client.clean("dir1/dir2")
.. code:: python
// Copy resource
client.copy(remote_path_from="dir1/file1", remote_path_to="dir2/file1")
client.copy(remote_path_from="dir2", remote_path_to="dir3")
.. code:: python
// Move resource
client.move(remote_path_from="dir1/file1", remote_path_to="dir2/file1")
client.move(remote_path_from="dir2", remote_path_to="dir3")
.. code:: python
// Move resource
client.download_sync(remote_path="dir1/file1", local_path="~/Downloads/file1")
client.download_sync(remote_path="dir1/dir2/", local_path="~/Downloads/dir2/")
.. code:: python
// Unload resource
client.upload_sync(remote_path="dir1/file1", local_path="~/Documents/file1")
client.upload_sync(remote_path="dir1/dir2/", local_path="~/Documents/dir2/")
.. code:: python
// Publish the resource
link = client.publish("dir1/file1")
link = client.publish("dir2")
.. code:: python
// Unpublish resource
client.unpublish("dir1/file1")
client.unpublish("dir2")
.. code:: python
// Exception handling
from webdav.client import WebDavException
try:
...
except WebDavException as exception:
...
.. code:: python
// Get the missing files
client.pull(remote_directory='dir1', local_directory='~/Documents/dir1')
.. code:: python
// Send missing files
client.push(remote_directory='dir1', local_directory='~/Documents/dir1')
**Asynchronous methods**
.. code:: python
// Load resource
kwargs = {
'remote_path': "dir1/file1",
'local_path': "~/Downloads/file1",
'callback': callback
}
client.download_async(**kwargs)
kwargs = {
'remote_path': "dir1/dir2/",
'local_path': "~/Downloads/dir2/",
'callback': callback
}
client.download_async(**kwargs)
.. code:: python
// Unload resource
kwargs = {
'remote_path': "dir1/file1",
'local_path': "~/Downloads/file1",
'callback': callback
}
client.upload_async(**kwargs)
kwargs = {
'remote_path': "dir1/dir2/",
'local_path': "~/Downloads/dir2/",
'callback': callback
}
client.upload_async(**kwargs)
Resource API
============
Resource API using the concept of OOP that enables cloud-level
resources.
.. code:: python
// Get a resource
res1 = client.resource("dir1/file1")
.. code:: python
// Work with the resource
res1.rename("file2")
res1.move("dir1/file2")
res1.copy("dir2/file1")
info = res1.info()
res1.read_from(buffer)
res1.read(local_path="~/Documents/file1")
res1.read_async(local_path="~/Documents/file1", callback)
res1.write_to(buffer)
res1.write(local_path="~/Downloads/file1")
res1.write_async(local_path="~/Downloads/file1", callback)
wdc
===
wdc \-a cross-platform utility that provides convenient work with
WebDAV-servers right from your console. In addition to full
implementations of methods from webdav API, also added methods content
sync local and remote directories.
**Authentication**
- *Basic authentication*
.. code:: bash
$ wdc login https://wedbav.server.ru -p http://127.0.0.1:8080
webdav_login: w_login
webdav_password: w_password
proxy_login: p_login
proxy_password: p_password
success
- Authorize the application using OAuth token\*
.. code:: bash
$ wdc login https://wedbav.server.ru -p http://127.0.0.1:8080 --token xxxxxxxxxxxxxxxxxx
proxy_login: p_login
proxy_password: p_password
success
There are also additional keys ``--root[-r]``, ``--cert-path[-c]`` and
``--key-path[-k]``.
**Utility**
.. code:: bash
$ wdc check
success
$ wdc check file1
not success
$ wdc free
245234120344
$ wdc ls dir1
file1
...
fileN
$ wdc mkdir dir2
$ wdc copy dir1/file1 -t dir2/file1
$ wdc move dir2/file1 -t dir2/file2
$ wdc download dir1/file1 -t ~/Downloads/file1
$ wdc download dir1/ -t ~/Downloads/dir1/
$ wdc upload dir2/file2 -f ~/Documents/file1
$ wdc upload dir2/ -f ~/Documents/
$ wdc publish di2/file2
https://yadi.sk/i/vWtTUcBucAc6k
$ wdc unpublish dir2/file2
$ wdc pull dir1/ -t ~/Documents/dir1/
$ wdc push dir1/ -f ~/Documents/dir1/
$ wdc info dir1/file1
{'name': 'file1', 'modified': 'Thu, 23 Oct 2014 16:16:37 GMT',
'size': '3460064', 'created': '2014-10-23T16:16:37Z'}
WebDAV-server
=============
The most popular cloud-based repositories that support the Protocol
WebDAV can be attributed Yandex.Drive, Dropbox, Google Drive, Box and
4shared. Access to data repositories, operating with access to the
Internet. If necessary local locations and cloud storage, you can deploy
your own WebDAV-server.
**Local WebDAV-server**
To deploy a local WebDAV server, using Docker containers quite easily
and quickly. To see an example of a local deploymentWebDAV servers can
be on the project
`webdav-server-docker <https://github.com/designerror/webdav-server-docker>`__.
**Supported methods**
+----------------+--------+--------+--------+---------+---------+--------+--------+------------+----------+
| Servers | free | info | list | mkdir | clean | copy | move | download | upload |
+================+========+========+========+=========+=========+========+========+============+==========+
| Yandex.Disk | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+ |
+----------------+--------+--------+--------+---------+---------+--------+--------+------------+----------+
| Dropbox | \- | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+ |
+----------------+--------+--------+--------+---------+---------+--------+--------+------------+----------+
| Google Drive | \- | \+ | \+ | \+ | \+ | \- | \- | \+ | \+ |
+----------------+--------+--------+--------+---------+---------+--------+--------+------------+----------+
| Box | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+ | \+ |
+----------------+--------+--------+--------+---------+---------+--------+--------+------------+----------+
| 4shared | \- | \+ | \+ | \+ | \+ | \- | \- | \+ | \+ |
+----------------+--------+--------+--------+---------+---------+--------+--------+------------+----------+
| Webdavserver | \- | \+ | \+ | \+ | \+ | \- | \- | \+ | \+ |
+----------------+--------+--------+--------+---------+---------+--------+--------+------------+----------+
Publish and unpublish methods supports only Yandex.Disk.
**Configuring connections**
To work with cloud storage Dropbox and Google Drive via the WebDAV
Protocol, you must use a WebDAV-server DropDAV and DAV-pocket,
respectively.
A list of settings for WebDAV servers:
.. code:: yaml
webdav-servers:
- yandex
hostname: https://webdav.yandex.ru
login: #login_for_yandex
password: #pass_for_yandex
- dropbox
hostname: https://dav.dropdav.com
login: #login_for dropdav
password: #pass_for_dropdav
- google
hostname: https://dav-pocket.appspot.com
root: docso
login: #login_for_dav-pocket
password: #pass_for_dav-pocket
- box
hostname: https://dav.box.com
root: dav
login: #login_for_box
password: #pass_for_box
- 4shared
hostname: https://webdav.4shared.com
login: #login_for_4shared
password: #pass_for_4shared
Autocompletion
==============
For macOS, or older Unix systems you need to update bash.
.. code:: bash
$ brew install bash
$ chsh
$ brew install bash-completion
Autocompletion can be enabled globally
.. code:: bash
$ sudo activate-global-python-argcomplete
or locally
.. code:: bash
#.bashrc
eval "$(register-python-argcomplete wdc)"
.. |PyPI version| image:: https://badge.fury.io/py/webdavclient.svg
:target: http://badge.fury.io/py/webdavclient
.. |Requirements Status| image:: https://requires.io/github/designerror/webdav-client-python/requirements.svg?branch=master&style=flat
:target: https://requires.io/github/designerror/webdav-client-python/requirements/?branch=master&style=flat
.. |PullReview stats| image:: https://www.pullreview.com/github/designerror/webdavclient/badges/master.svg?
:target: https://www.pullreview.com/github/designerror/webdavclient/reviews/master
.. |Github| image:: https://github.com/favicon.ico

688
TESTING.md Normal file
View file

@ -0,0 +1,688 @@
Тестирование
===
В пакет Webdavclient включены следующие компоненты:
- `webdav API`
- `resource API`
- `wdc`
Каждый из компонентов имеет свою <span style="text-decoration: underline">тестовую базу</span>.
### webdav API ###
Компонент webdav API содержит следующие <span style="text-decoration: underline">тестовые наборы</span>:
- настройка подключения
- аутентификация
- методы
- интеграционные тесты
#### Настройка подключения ####
Для инициализации клиента используется словарь (настройки подключения), содержащий набор опций подключения к webdav-серверу.
Настройки подключения имеют обязательные и не обязательные опции. К обязательным опциям относятся `webdav_hostname`, `webdav_login` и `webdav_password`.
Заполнение полей может быть валидным или не валидным.
Для проверки используются метод `valid?`.
```python
import webdav.client as wc
options = {
'webdav_hostname': "https://webdav.server.ru",
'webdav_login': "login",
'webdav_password': "password"
}
client = wc.Client(options)
client.valid?
```
*Тестовый сценарий 1*
Идентификатор: 1.1.1
Название: Обязательные опции подключения
Описание: В случае присутствия каждой из обязательных опций,
настройки подключения клиента будут валидными.
<span style="color: red">Красный случай</span>
```python
assert_that(client, is_(not_valid())
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(client, is_(valid())
```
*Тестовый сценарий 2*
Идентификатор: 1.1.2
Название: Валидность обязательных опций подключения
Описание: В случае валидности обязательных опций,
настройки подключения клиента будут валидными.
<span style="color: red">Красный случай</span>
```python
#without webdav_hostname
#without webdav_login
#without webdav_password
#with proxy_login or proxy_password, but without proxy_hostname
#with proxy_password and proxy_hostname, but without proxy_login
assert_that(client, is_(not_valid())
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(client, is_(valid())
```
*Тестовый сценарий 3*
Идентификатор: 1.1.3
Название: Валидность сертификата
Описание: При указании валидного файла и ключа сертификата,
настройки подключения клиента будут валидными.
<span style="color: red">Красный случай</span>
```python
#with key_path, but without cert_path
#key_path or cert_path not exists
assert_that(calling(client.is_valid), raises(CertificateNotValid))
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.is_valid), is_not(raises(CertificateNotValid))
```
#### Аутентификация ####
При подключении к webdav-серверу, необходимо пройти у него basic-аутентификацию, для этого используются опции `webdav_login` и `webdav_password`. При наличии proxy-сервера, может потребоваться так же пройти аутентификацию и у proxy-сервера. Для proxy-сервера поддерживаются следующие виды аутентификации:
- `basic`
- `digest`
- `ntlm`
- `negotiate`
*Тестовый сценарий 1*
Идентификатор: 1.2.1
Название: Аутентификация с webdav-сервером
Описание: При правильной аутентификации клиент подключается к webdav-серверу.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.check), is_(not_suceess())
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.check), is_(suceess())
```
*Тестовый сценарий 2*
Идентификатор: 1.2.2
Название: Аутентификация с proxy-сервером
Описание: При правильной аутентификации клиент подключается к webdav-серверу.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.check), is_(not_suceess())
```
<span style="color: green">Зеленый случай</span>
```python
#basic
#digest
#ntlm
#negotiate
assert_that(calling(client.check), is_(suceess())
```
#### Методы ####
webdav API реализует следущие методы: `check`, `free`, `info`, `list`, `mkdir`, `clean`, `copy`, `move`, `download`, `upload`, `publish` и `unpublish`.
*Тестовый сценарий 1*
Идентификатор: 1.3.1
Название: Проверка существования ресурса
Описание: В случае существования ресурса, результат выполнения метода check
будет успешным.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.check).with_args(remote_path), is_(not_suceess())
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.check).with_args(remote_path), is_(suceess())
```
*Тестовый сценарий 2*
Идентификатор: 1.3.2
Название: Проверка свободного места
Описание: В случае если webdav-сервер поддерживает метод free, метод возвращает
размер свободного места.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.free), raises(MethodNotSupported))
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.free), greater_than(0))
```
*Тестовый сценарий 3*
Идентификатор: 1.3.3
Название: Получение информации о ресурсе
Описание: В случае если webdav-сервер поддерживает метод info,
метод возвращает информацию следующего типа:
- дата создания;
- дата модификации;
- размер;
- имя.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.info).with_args(remote_path), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
info = client(remote_path)
assert_that(info, has_key("data1"))
assert_that(info, has_key("data2"))
assert_that(info, has_key("size"))
assert_that(info, has_key("name"))
```
*Тестовый сценарий 4*
Идентификатор: 1.3.4
Название: Получение списка ресурсов
Описание: В случае, если указанный ресурс существует и является директорией, то метод list
возвращает список ресурсов, находящихся в данном ресурсе.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.info).with_args(remote_file), raises(RemoteResourceNotFound))
assert_that(calling(client.list).with_args(remote_path), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
files = client.list(remote_path)
assert_that(files, not_none()))
```
*Тестовый сценарий 5*
Идентификатор: 1.3.5
Название: Создание директории
Описание: В случае, если все директории из путевого разбиения для
указанного ресурса существуют, то данный ресурс будет создан.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.info).with_args(remote_path), raises(RemoteParentNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
client.mkdir(remote_path)
assert_that(calling(client.check).with_args(remote_path), is_(success()))
```
*Тестовый сценарий 6*
Идентификатор: 1.3.6
Название: Удаление ресурса
Описание: В случае, если указанный ресурс существует и не является корнем, то
метод clean удалит данный ресурс.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.clean).with_args(remote_path), raises(RemoteResourceNotFound))
assert_that(calling(client.clean).with_args(root), raises(InvalidOption))
```
<span style="color: green">Зеленый случай</span>
```python
client.clean(remote_path)
assert_that(calling(client.check).with_args(remote_path), is_(not_success()))
```
*Тестовый сценарий 7*
Идентификатор: 1.3.7
Название: Копирование ресурса
Описание: В случае, если указанный ресурс существует и не является корнем, то
метод copy копирует данный ресурс.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.copy).with_args(from_path=remote_path, to_path=new_path), raises(RemoteResourceNotFound))
assert_that(calling(client.copy).with_args(from_path=root, to_path=new_path), raises(InvalidOption))
assert_that(calling(client.copy).with_args(from_path=remote_path, to_path=root), raises(InvalidOption))
assert_that(calling(client.copy).with_args(from_path=remote_path, to_path=remote_path), is_not(raises(WebDavException)))
```
<span style="color: green">Зеленый случай</span>
```python
client.copy(from_path=remote_path, to_path=new_path)
assert_that(calling(client.check).with_args(new_path), is_(success()))
```
*Тестовый сценарий 8*
Идентификатор: 1.3.8
Название: Перемещение ресурса
Описание: В случае, если указанный ресурс существует и не является корнем, то
метод move переместит данный ресурс.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.move).with_args(from_path=old_path, to_path=new_path), raises(RemoteResourceNotFound))
assert_that(calling(client.move).with_args(from_path=root, to_path=new_path), raises(InvalidOption))
assert_that(calling(client.move).with_args(from_path=old_path, to_path=root), raises(InvalidOption))
assert_that(calling(client.move).with_args(from_path=old_path, to_path=remote_path), is_not(raises(WebDavException)))
```
<span style="color: green">Зеленый случай</span>
```python
client.move(from_path=old_path, to_path=new_path)
assert_that(calling(client.check).with_args(old_path), is_(not_success()))
assert_that(calling(client.check).with_args(new_path), is_(success()))
```
*Тестовый сценарий 9*
Идентификатор: 1.3.9
Название: Загрузка ресурса
Описание: В случае, если указанный ресурс существует,
то метод download загрузит данный ресурс.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.download).with_args(remote_path=remote_path, local_path=local_path), raises(LocalResourceNotFound))
assert_that(calling(client.download).with_args(remote_path=remote_path, local_path=local_path), raises(RemoteResourceNotFound))
assert_that(calling(client.download).with_args(remote_path=remote_file, local_path=local_directory), raises(InvalidOption))
assert_that(calling(client.download).with_args(remote_path=remote_directory, local_path=local_file), raises(InvalidOption))
```
<span style="color: green">Зеленый случай</span>
```python
client.download(remote_path=remote_path, local_path=local_path)
assert_that(local_path, is_(exist()))
```
*Тестовый сценарий 10*
Идентификатор: 1.3.10
Название: Выгрузка ресурса
Описание: В случае, если родительская директория указанный ресурса
существует, то метод upload выгрузит файл или директорию в
ресурс.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.upload).with_args(remote_path=remote_path, local_path=local_path), raises(RemoteParentNotFound))
assert_that(calling(client.upload).with_args(remote_path=remote_file, local_path=local_directory), raises(InvalidOption))
assert_that(calling(client.upload).with_args(remote_path=remote_directory, local_path=local_file), raises(InvalidOption))
```
<span style="color: green">Зеленый случай</span>
```python
client.upload(remote_path=remote_path, to_path=local_path)
assert_that(calling(client.check).with_args(remote_path), is_(success()))
```
*Тестовый сценарий 11*
Идентификатор: 1.3.11
Название: Публикация ресурса
Описание: В случае, если указанный ресурс существует, то метод publish
возвращает публичную ссылку на ресурс.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.publish).with_args(remote_path), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.publish).with_args(remote_path), is_not(raises(RemoteResourceNotFound))
link = client.publish(remote_path)
assert_that(link, starts_with("http")
```
*Тестовый сценарий 12*
Идентификатор: 1.3.12
Название: Отмена публикации ресурса
Описание: В случае, если указанный ресурс существует,
то метод unpublish отменяет публикацию ресурса.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.unpublish).with_args(remote_path), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.unpublish).with_args(remote_path), is_not(raises(RemoteResourceNotFound))
```
### resource API ###
Компонент resource API состоит из следующих <span style="text-decoration: underline">тестовых наборов</span> методы:
- получение ресурса
- методы
#### Получение ресурса ####
Для получение ресурса, используется метод `resource`.
*Тестовый сценарий 1*
Идентификатор: 2.1.1
Название: Получение ресурса
Описание: В случае, если указанный ресурс является директорией и существует,
то метод resource возвращает ресурс.
В случае, если указанный ресурс является файлом,
то метод resource возвращает ресурс.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.resource).with_args(remote_directory), raises(RemoteResourceNotFound))
assert_that(calling(client.resource).with_args(remote_file), is_not(raises(RemoteResourceNotFound)))
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.check).with_args(remote_path), is_(success())
res = client.resource(remote_path)
assert_that(res.check())
```
#### Методы ####
resource API реализует следущие методы: `check`, `clean`, `is_directory`, `rename`, `move`, `copy`, `info`, `read_from`, `read`, `read_async`, `write_to`, `write`, `write_async`, `publish` и `unpublish`.
*Тестовый сценарий 1*
Идентификатор: 2.2.1
Название: Проверка существования ресурса
Описание: В случае, если указанный ресурс существует,
то результат метода check будет успешным.
<span style="color: red">Красный случай</span>
```python
assert_that(calling(client.resource).with_args(remote_path), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.check).with_args(remote_path), is_(success())
res = client.resource(remote_path)
assert_that(res.check())
```
*Тестовый сценарий 2*
Идентификатор: 2.2.2
Название: Удаление ресурса
Описание: В случае, если указанный ресурс существует,
то метод clean удалит данный ресурс.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.clean), is_not(raises(RemoteResourceNotFound)))
```
<span style="color: green">Зеленый случай</span>
```python
assert_that(calling(client.check).with_args(remote_path), is_(success())
res = client.resource(remote_path)
assert_that(res.check())
```
*Тестовый сценарий 3*
Идентификатор: 2.2.3
Название: Проверка является ли ресурс директорией
Описание: В случае, если указанный ресурс является директорией,
то результат метода is_directory будет успешным.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_file)
assert_that(calling(res.is_directory), is_(not_success()))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(remote_directory)
assert_that(calling(res.is_directory), is_(success()))
```
*Тестовый сценарий 4*
Идентификатор: 2.2.4
Название: Переименование ресурса
Описание: В случае, если указанный ресурс существует,
то метод rename переименует данный ресурс.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.rename).with_args(new_name), raises(RemoteResourceNotFound))
assert_that(calling(res.rename).with_args(new_name), raises(RemoteResourceAlreadyExists))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(old_path)
res.rename(new_name)
new_path = res.urn
assert_that(calling(client.check).with_args(old_path), is_(not_success()))
assert_that(calling(client.check).with_args(new_path), is_(success()))
```
*Тестовый сценарий 5*
Идентификатор: 2.2.5
Название: Перемещение ресурса
Описание: В случае, если указанный ресурс существует,
то метод move переместит данный ресурс.
<span style="color: red">Красный случай</span>
```python
res = client.resource(old_path)
assert_that(calling(res.move).with_args(new_path), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(old_path)
res.move(new_path)
assert_that(calling(client.check).with_args(old_path), is_(not_success()))
assert_that(calling(client.check).with_args(new_path), is_(success()))
```
*Тестовый сценарий 6*
Идентификатор: 2.2.6
Название: Копирование ресурса
Описание: В случае, если указанный ресурс существует,
то метод copy скопирует данный ресурс.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.copy).with_args(to_path), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(remote_path)
res.copy(new_path)
assert_that(calling(client.check).with_args(remote_path), is_(success()))
assert_that(calling(client.check).with_args(new_path), is_(success()))
```
*Тестовый сценарий 7*
Идентификатор: 2.2.7
Название: Получение информации о ресурсе
Описание: В случае, если указанный ресурс существует,
то метод info возвращает информацию следующего типа:
- дата создания;
- дата модификации;
- размер;
- имя.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.info), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(remote_path)
info = res.info()
assert_that(info, has_key("data1"))
assert_that(info, has_key("data2"))
assert_that(info, has_key("size"))
assert_that(info, has_key("name"))
```
*Тестовый сценарий 8*
Идентификатор: 2.2.8
Название: Считывание данных с буфера в ресурс
Описание: В случае, если указанный ресурс не является директорией,
то метод read_from считывет содержимое буфера и записывает в ресурс.
<span style="color: red">Красный случай</span>
```python
res1 = client.resource(remote_file)
assert_that(buff, is_(empty))
assert_that(calling(res1.read_from).with_args(buff), raises(BufferIsEmpty))
res2 = client.resource(remote_directory)
assert_that(calling(res2.read_from).with_args(buff), raises(ResourceIsNotDirectory))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(remote_path)
res.read_from(buff)
res_size = res.info("size")
assert_that(buff.size(), equal_to(res_size))
```
*Тестовый сценарий 9*
Идентификатор: 2.2.9
Название: Запись данных в буфер
Описание: В случае, если указанный ресурс не является директорией,
то метод write_to записывает содержимое ресурса в буфер.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.write_to).with_args(buff), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(remote_path)
res.write_to(buff)
res_size = res.info("size")
assert_that(buff.size(), equal_to(res_size))
```
*Тестовый сценарий 10*
Идентификатор: 2.2.10
Название: Публикация ресурса
Описание: В случае, если указанный ресурс существует, то метод publish
возвращает публичную ссылку на ресурс.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.publish), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.publish), is_not(raises(RemoteResourceNotFound))
link = res.publish()
assert_that(link, starts_with("http")
```
*Тестовый сценарий 11*
Идентификатор: 2.2.11
Название: Отмена публикации ресурса
Описание: В случае, если указанный ресурс существует,
то метод unpublish отменяет публикацию ресурса.
<span style="color: red">Красный случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.unpublish), raises(RemoteResourceNotFound))
```
<span style="color: green">Зеленый случай</span>
```python
res = client.resource(remote_path)
assert_that(calling(res.unpublish).with_args(remote_path), is_not(raises(RemoteResourceNotFound))
```

1
THANKS.md Normal file
View file

@ -0,0 +1 @@
- [cesterlizi](https://github.com/cesterlizi)

79
setup.py Normal file
View file

@ -0,0 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
from setuptools.command.install import install as InstallCommand
version = "0.1"
requirements = "libxml2-dev libxslt-dev python-dev"
class Install(InstallCommand):
def run(self):
#params = "{install_params} {requirements}".format(install_params="install", requirements=requirements)
#cmd = "{command} {params}".format(command="apt-get", params=params)
#proc = subprocess.Popen(cmd, shell=True)
# proc.wait()
InstallCommand.run(self)
class Test(TestCommand):
user_options = [('pytest-args=', 'a', "")]
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = []
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
import pytest
errno = pytest.main(self.pytest_args)
sys.exit(errno)
setup(
name='webdavclient2',
version=version,
packages=find_packages(),
requires=['python (>= 2.7.6)'],
install_requires=['requests', 'lxml'],
scripts=['wdc'],
tests_require=['pytest', 'pyhamcrest', 'junit-xml', 'pytest-allure-adaptor'],
cmdclass={'install': Install, 'test': Test},
description='Webdav API, resource API и wdc для WebDAV-серверов (Yandex.Disk, Dropbox, Google Disk, Box, 4shared и т.д.)',
long_description=open('README.rst').read(),
author='Designerror',
author_email='designerror@yandex.ru',
url='https://github.com/designerror/webdavclient',
download_url='https://github.com/designerror/webdavclient/tarball/master',
license='MIT License',
keywords='webdav, client, python, module, library, packet, Yandex.Disk, Dropbox, Google Disk, Box, 4shared',
classifiers=[
'Environment :: Console',
'Environment :: Web Environment',
'License :: OSI Approved :: MIT License',
'Operating System :: MacOS',
'Operating System :: Microsoft',
'Operating System :: Unix',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.0',
'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Internet',
'Topic :: Software Development :: Libraries :: Python Modules',
],
)

32
tests/conftest.py Normal file
View file

@ -0,0 +1,32 @@
__author__ = 'designerror'
from hamcrest.core.base_matcher import BaseMatcher
from hamcrest.core.helpers.hasmethod import hasmethod
class Valid(BaseMatcher):
def _matches(self, item):
return False if not hasmethod(item, 'valid') else item.valid()
class NotValid(BaseMatcher):
def _matches(self, item):
return False if not hasmethod(item, 'valid') else not item.valid()
class Success(BaseMatcher):
def _matches(self, item):
return item
class NotSuccess(BaseMatcher):
def _matches(self, item):
return not item
def valid():
return Valid()
def not_valid():
return NotValid()
def success():
return Success()
def not_success():
return NotSuccess()

View file

@ -0,0 +1 @@
__author__ = 'designerror'

View file

@ -0,0 +1,11 @@
__author__ = 'designerror'
import allure
#from hamcrest import *
class TestRequiredOptions:
def test_without_webdav_hostname(self):
options = { 'webdav_server': "https://webdav.yandex.ru"}
allure.attach('options', options.__str__())
assert 1

View file

@ -0,0 +1 @@
__author__ = 'designerror'

13
tox.ini Normal file
View file

@ -0,0 +1,13 @@
[tox]
envlist = py27
toxworkdir={toxinidir}/../.tox
[testenv]
deps=
pycurl
lxml
argcomplete
pytest
pyhamcrest
junit-xml
pytest-allure-adaptor
commands=python setup.py test -a "--alluredir /var/tmp/allure"

621
wdc Normal file
View file

@ -0,0 +1,621 @@
#!/usr/bin/python
# PYTHON_ARGCOMPLETE_OK
from __future__ import print_function
import sys
import os
import shlex
import struct
import platform
import subprocess
import getpass
import argparse
import argcomplete
from webdav.client import Client, WebDavException, NotConnection, Urn
from distutils.util import strtobool
from base64 import b64decode, b64encode
def get_terminal_size():
current_os = platform.system()
tuple_xy = None
if current_os == 'Windows':
tuple_xy = _get_terminal_size_windows()
if tuple_xy is None:
tuple_xy = _get_terminal_size_input()
if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'):
tuple_xy = _get_terminal_size_linux()
if tuple_xy is None:
tuple_xy = 80, 25
return tuple_xy
def _get_terminal_size_windows():
try:
from ctypes import windll, create_string_buffer
h = windll.kernel32.GetStdHandle(-12)
csbi = create_string_buffer(22)
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
if res:
(bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
sizex = right - left + 1
sizey = bottom - top + 1
return sizex, sizey
except:
pass
def _get_terminal_size_input():
try:
cols = int(subprocess.check_call(shlex.split('tput cols')))
rows = int(subprocess.check_call(shlex.split('tput lines')))
return (cols, rows)
except:
pass
def _get_terminal_size_linux():
def ioctl_GWINSZ(fd):
try:
import fcntl
import termios
return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
except:
pass
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
try:
cr = (os.environ['LINES'], os.environ['COLUMNS'])
except:
return None
return int(cr[1]), int(cr[0])
class ProgressBar:
def __init__(self):
self.precise = 0
self.total = 0
def show(self):
progress_line = self._progress_line()
import sys
if self.precise == 100:
print(progress_line, end="")
else:
print(progress_line, end="")
sys.stdout.write("\r")
def _progress_line(self):
sizex, sizey = get_terminal_size()
available_width = sizex
precise = self._get_field_precise()
ratio = self._get_field_ratio()
available_width -= len(precise) + len(ratio)
progress = self._get_field_progress(available_width-2)
return "{precise} {progress} {ratio}".format(precise=precise, progress=progress, ratio=ratio)
def _get_field_precise(self):
line = "{precise}%".format(precise=self.precise)
return "{:<4}".format(line)
def _get_field_ratio(self):
current = float(self.precise * self.total / 100)
total = float(self.total)
current_line = "{:.2f}".format(current)
total_line = "{:.2f}".format(total)
line = "{current}/{total}".format(current=current_line, total=total_line)
available = len(total_line) * 2 + 1
format_line = "{prefix}{value}{sufix}".format(prefix="{:>", value=available, sufix="}")
line = format_line.format(line)
return line
def _get_field_progress(self, width):
available_width = width - 2
current = int(self.precise * available_width / 100)
available_width -= current + 1
tip = ">" if not self.precise == 100 else ""
progress = "{arrow}{tip}{space}".format(arrow="=" * current, tip=tip, space=" " * available_width)
return "[{progress}]".format(progress=progress)
def callback(self, current, total):
if total and not self.total:
self.total = total
if not total:
return
precise = int(float(current) * 100 / total)
if self.precise == precise:
return
else:
self.precise = precise
self.show()
setting_keys = ['webdav_hostname', 'webdav_root', 'webdav_login', 'webdav_password', 'webdav_token',
'proxy_hostname', 'proxy_login', 'proxy_password',
'cert_path', 'key_path']
crypto_keys = ['webdav_password', 'webdav_token', 'proxy_password']
def encoding(source):
if not source:
return ""
return b64encode(source.encode('utf-8'))
def decoding(source):
if not source:
return ""
return b64decode(source).decode('utf-8')
def import_options():
options = dict()
for setting_key in setting_keys:
options[setting_key] = os.environ.get(setting_key.upper())
for crypto_key in crypto_keys:
if not options[crypto_key]:
continue
options[crypto_key] = decoding(options[crypto_key])
return options
def valid(options):
if 'webdav_hostname' not in options:
return False
if not options['webdav_token'] and not options['webdav_login']:
return False
if options['webdav_login'] and not options['webdav_password']:
return False
return True
class Formatter(argparse.RawTextHelpFormatter):
def _get_default_metavar_for_optional(self, action):
if not action.option_strings:
return action.dest
else:
return ""
def _format_action_invocation(self, action):
if not action.option_strings:
default = self._get_default_metavar_for_optional(action)
metavar, = self._metavar_formatter(action, default)(1)
return metavar
else:
parts = []
if action.nargs == 0:
parts.extend(action.option_strings)
else:
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
for option_string in action.option_strings:
parts.append(option_string)
return '%s %s' % (', '.join(parts), args_string)
return ', '.join(parts)
def _metavar_formatter(self, action, default_metavar):
if action.metavar is not None:
result = action.metavar
else:
result = default_metavar
def format(tuple_size):
if isinstance(result, tuple):
return result
else:
return (result, ) * tuple_size
return format
def logging_exception(exception):
print(exception)
def urn_completer(prefix, **kwargs):
options = import_options()
try:
client = Client(options)
prefix_urn = Urn(prefix)
if prefix_urn.is_dir():
return (prefix+filename for filename in client.list(prefix_urn.path()))
else:
parent = prefix_urn.parent()
prefix_filename = prefix_urn.filename()
prefix_filename_length = len(prefix_filename)
return (prefix + filename[prefix_filename_length:] for filename in client.list(parent) if filename.startswith(prefix_filename))
except WebDavException:
pass
return tuple()
if __name__ == "__main__":
epilog = """
Examples:
--------
$ wdc login https://webdav.server.ru
webdav_login: login
webdav_password: password
success
$ wdc login https://webdav.server.ru --token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
not success
$ wdc check
success
$ wdc check file1
not success
$ wdc free
245234120344
$ wdc ls dir1
file1
...
fileN
$ wdc mkdir dir2
$ wdc copy dir1/file1 -t dir2/file1
$ wdc move dir2/file1 -t dir2/file2
$ wdc download dir1/file1 -t ~/Downloads/file1
$ wdc download dir1/ -t ~/Downloads/dir1/
$ wdc upload dir2/file2 -f ~/Documents/file1
$ wdc upload dir2/ -f ~/Documents/
$ wdc publish di2/file2
https://yadi.sk/i/vWtTUcBucAc6k
$ wdc unpublish dir2/file2
$ wdc pull dir1/ -t ~/Documents/dir1/
$ wdc push dir1/ -f ~/Documents/dir1/
$ wdc info dir1/file1
{'name': 'file1', 'modified': 'Thu, 23 Oct 2014 16:16:37 GMT',
'size': '3460064', 'created': '2014-10-23T16:16:37Z'}
"""
usage = """
wdc [-h] [-v]
wdc login https://webdav.server.ru [--token] [-r] [-p] [-c] [-k]
wdc [action] [path] [-t] [-f]
"""
actions = "login logout check info free ls clean mkdir copy move download upload publish unpublish push pull".split()
actions_help = "check, info, free, ls, clean, mkdir, copy, move,\ndownload, upload, publish, unpublish, push, pull"
parser = argparse.ArgumentParser(prog='wdc', formatter_class=Formatter, epilog=epilog, usage=usage)
parser.add_argument("action", help=actions_help, choices=actions)
from webdav.client import __version__ as version
version_text = "{name} {version}".format(name="%(prog)s", version=version)
parser.add_argument("-v", '--version', action='version', version=version_text)
parser.add_argument("-r", "--root", help="example: dir1/dir2")
parser.add_argument("--token", help="example: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
parser.add_argument("-c", "--cert-path", help="example: /etc/ssl/certs/certificate.crt")
parser.add_argument("-k", "--key-path", help="example: /etc/ssl/private/certificate.key")
parser.add_argument("-p", "--proxy", help="example: http://127.0.0.1:8080")
parser.add_argument("path", help="example: dir1/dir2/file1", nargs='?').completer = urn_completer
parser.add_argument("-f", '--from-path', help="example: ~/Documents/file1")
parser.add_argument("-t", "--to-path", help="example for download and pull: ~/Download/file1\nexample for copy and move: dir1/dir2").completer = urn_completer
argcomplete.autocomplete(parser, exclude=("-h", "--help", "--proxy", "-p", "-r", "--root", "-c", "--cert-path", "-t", "--to-path", "-v", "--version", "-f", "--from-path", "-k", "--key-path"))
args = parser.parse_args()
action = args.action
if action == 'login':
env = dict()
if not args.path:
try:
env['webdav_hostname'] = raw_input("webdav_hostname: ")
except NameError:
env['webdav_hostname'] = input("webdav_hostname: ")
else:
env['webdav_hostname'] = args.path
if not args.token:
try:
env['webdav_login'] = raw_input("webdav_login: ")
except NameError:
env['webdav_login'] = input("webdav_login: ")
env['webdav_password'] = getpass.getpass("webdav_password: ")
else:
env['webdav_token'] = args.token
if args.proxy:
env['proxy_hostname'] = args.proxy
try:
env['proxy_login'] = raw_input("proxy_login: ")
except NameError:
env['proxy_login'] = input("proxy_login: ")
env['proxy_password'] = getpass.getpass("proxy_password: ")
if args.root:
env['webdav_root'] = args.root
if args.cert_path:
env['cert_path'] = args.cert_path
if args.key_path:
env['key_path'] = args.key_path
try:
client = Client(env)
check = client.check()
text = "success" if check else "not success"
print(text)
if check:
for crypto_key in crypto_keys:
if crypto_key not in env:
continue
if not env[crypto_key]:
continue
env[crypto_key] = encoding(env[crypto_key])
for (key, value) in env.items():
os.putenv(key.upper(), value)
os.system('bash')
except WebDavException as e:
print("not success")
sys.exit()
else:
options = import_options()
if not valid(options):
print("First log on webdav server using the following command: wdc login.")
sys.exit()
elif action == "logout":
os.system("exit")
elif action == 'check':
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
check = client.check(args.path) if args.path else client.check()
text = "success" if check else "not success"
print(text)
except WebDavException as e:
logging_exception(e)
elif action == 'free':
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
free_size = client.free()
print(free_size)
except WebDavException as e:
logging_exception(e)
elif action == 'ls':
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
paths = client.list(args.path) if args.path else client.list()
for path in paths:
print(path)
except WebDavException as e:
logging_exception(e)
elif action == 'clean':
if not args.path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
client.clean(args.path)
except WebDavException as e:
logging_exception(e)
elif action == 'mkdir':
if not args.path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
client.mkdir(args.path)
except WebDavException as e:
logging_exception(e)
elif action == 'copy':
if not args.path or not args.to_path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
client.copy(remote_path_from=args.path, remote_path_to=args.to_path)
except WebDavException as e:
logging_exception(e)
elif action == 'move':
if not args.path or not args.to_path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
client.move(remote_path_from=args.path, remote_path_to=args.to_path)
except WebDavException as e:
logging_exception(e)
elif action == 'download':
if not args.path or not args.to_path:
parser.print_help()
else:
options = import_options()
progress_bar = ProgressBar()
def download_progress(download_t, download_d, upload_t, upload_d):
progress_bar.callback(current=download_d, total=download_t)
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
if not os.path.exists(path=args.to_path):
client.download(remote_path=args.path, local_path=args.to_path, progress=download_progress)
print("\n")
else:
try:
choice = raw_input("Local path exists, do you want to overwrite it? [Y/n] ")
except NameError:
choice = input("Local path exists, do you want to overwrite it? [Y/n] ")
try:
yes = strtobool(choice.lower())
if yes:
client.download(remote_path=args.path, local_path=args.to_path, progress=download_progress)
print("\n")
except ValueError:
print("Incorrect answer")
except WebDavException as e:
logging_exception(e)
elif action == 'upload':
if not args.path or not args.from_path:
parser.print_help()
else:
options = import_options()
progress_bar = ProgressBar()
def upload_progress(download_t, download_d, upload_t, upload_d):
progress_bar.callback(current=upload_d, total=upload_t)
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
if not client.check(remote_path=args.path):
client.upload(remote_path=args.path, local_path=args.from_path, progress=upload_progress)
print("\n")
else:
try:
choice = raw_input("Remote resource exists, do you want to overwrite it? [Y/n] ")
except NameError:
choice = input("Remote resource exists, do you want to overwrite it? [Y/n] ")
try:
yes = strtobool(choice.lower())
if yes:
client.upload(remote_path=args.path, local_path=args.from_path, progress=upload_progress)
print("\n")
except ValueError:
print("Incorrect answer")
except WebDavException as e:
logging_exception(e)
elif action == 'publish':
if not args.path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
link = client.publish(args.path)
print(link)
except WebDavException as e:
logging_exception(e)
elif action == 'unpublish':
if not args.path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
client.unpublish(args.path)
except WebDavException as e:
logging_exception(e)
elif action == 'push':
if not args.path or not args.from_path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
client.push(remote_directory=args.path, local_directory=args.from_path)
except WebDavException as e:
logging_exception(e)
elif action == 'pull':
if not args.path or not args.to_path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
client.pull(remote_directory=args.path, local_directory=args.to_path)
except WebDavException as e:
logging_exception(e)
elif action == 'info':
if not args.path:
parser.print_help()
else:
options = import_options()
try:
client = Client(options)
connection = client.check()
if not connection:
raise NotConnection(options["webdav_hostname"])
info = client.info(args.path)
print(info)
except WebDavException as e:
logging_exception(e)
else:
parser.print_help()

0
webdav2/__init__.py Normal file
View file

1096
webdav2/client.py Normal file

File diff suppressed because it is too large Load diff

81
webdav2/connection.py Normal file
View file

@ -0,0 +1,81 @@
from webdav2.exceptions import *
from webdav2.urn import Urn
from os.path import exists
class ConnectionSettings:
def is_valid(self):
pass
def valid(self):
try:
self.is_valid()
except OptionNotValid:
return False
else:
return True
class WebDAVSettings(ConnectionSettings):
ns = "webdav:"
prefix = "webdav_"
keys = {'hostname', 'login', 'password', 'token', 'root', 'cert_path', 'key_path', 'recv_speed', 'send_speed', 'verbose'}
def __init__(self, options):
self.options = dict()
for key in self.keys:
value = options.get(key, '')
self.options[key] = value
self.__dict__[key] = value
self.root = Urn(self.root).quote() if self.root else ''
self.root = self.root.rstrip(Urn.separate)
def is_valid(self):
if not self.hostname:
raise OptionNotValid(name="hostname", value=self.hostname, ns=self.ns)
if self.cert_path and not exists(self.cert_path):
raise OptionNotValid(name="cert_path", value=self.cert_path, ns=self.ns)
if self.key_path and not exists(self.key_path):
raise OptionNotValid(name="key_path", value=self.key_path, ns=self.ns)
if self.key_path and not self.cert_path:
raise OptionNotValid(name="cert_path", value=self.cert_path, ns=self.ns)
if self.password and not self.login:
raise OptionNotValid(name="login", value=self.login, ns=self.ns)
if not self.token and not self.login:
raise OptionNotValid(name="login", value=self.login, ns=self.ns)
class ProxySettings(ConnectionSettings):
ns = "proxy:"
prefix = "proxy_"
keys = {'hostname', 'login', 'password'}
def __init__(self, options):
self.options = dict()
for key in self.keys:
value = options.get(key, '')
self.options[key] = value
self.__dict__[key] = value
def is_valid(self):
if self.password and not self.login:
raise OptionNotValid(name="login", value=self.login, ns=self.ns)
if self.login or self.password:
if not self.hostname:
raise OptionNotValid(name="hostname", value=self.hostname, ns=self.ns)

73
webdav2/exceptions.py Normal file
View file

@ -0,0 +1,73 @@
class WebDavException(Exception):
pass
class NotValid(WebDavException):
pass
class OptionNotValid(NotValid):
def __init__(self, name, value, ns=""):
self.name = name
self.value = value
self.ns = ns
def __str__(self):
return "Option ({ns}{name}={value}) have invalid name or value".format(ns=self.ns, name=self.name, value=self.value)
class CertificateNotValid(NotValid):
pass
class NotFound(WebDavException):
pass
class LocalResourceNotFound(NotFound):
def __init__(self, path):
self.path = path
def __str__(self):
return "Local file: {path} not found".format(path=self.path)
class RemoteResourceNotFound(NotFound):
def __init__(self, path):
self.path = path
def __str__(self):
return "Remote resource: {path} not found".format(path=self.path)
class RemoteParentNotFound(NotFound):
def __init__(self, path):
self.path = path
def __str__(self):
return "Remote parent for: {path} not found".format(path=self.path)
class MethodNotSupported(WebDavException):
def __init__(self, name, server):
self.name = name
self.server = server
def __str__(self):
return "Method {name} not supported for {server}".format(name=self.name, server=self.server)
class NotConnection(WebDavException):
def __init__(self, hostname):
self.hostname = hostname
def __str__(self):
return "Not connection with {hostname}".format(hostname=self.hostname)
class NotEnoughSpace(WebDavException):
def __init__(self):
pass
def __str__(self):
return "Not enough space on the server"

56
webdav2/urn.py Normal file
View file

@ -0,0 +1,56 @@
try:
from urllib.parse import unquote, quote
except ImportError:
from urllib import unquote, quote
from re import sub
class Urn(object):
separate = "/"
def __init__(self, path, directory=False):
self._path = quote(path)
expressions = "/\.+/", "/+"
for expression in expressions:
self._path = sub(expression, Urn.separate, self._path)
if not self._path.startswith(Urn.separate):
self._path = "{begin}{end}".format(begin=Urn.separate, end=self._path)
if directory and not self._path.endswith(Urn.separate):
self._path = "{begin}{end}".format(begin=self._path, end=Urn.separate)
def __str__(self):
return self.path()
def path(self):
return unquote(self._path)
def quote(self):
return self._path
def filename(self):
path_split = self._path.split(Urn.separate)
name = path_split[-2] + Urn.separate if path_split[-1] == '' else path_split[-1]
return unquote(name)
def parent(self):
path_split = self._path.split(Urn.separate)
nesting_level = self.nesting_level()
parent_path_split = path_split[:nesting_level]
parent = self.separate.join(parent_path_split) if nesting_level != 1 else Urn.separate
if not parent.endswith(Urn.separate):
return unquote(parent + Urn.separate)
else:
return unquote(parent)
def nesting_level(self):
return self._path.count(Urn.separate, 0, -1)
def is_dir(self):
return self._path[-1] == Urn.separate