Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proper install and cli and refactor code #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
config.py
__pycache__/
__pycache__/
Pipfile.lock
.mypy_cache
cache.json
20 changes: 20 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[scripts]
"list" = "invoke --list"
"help" = "invoke --help"
"login" = "invoke login"
"list-files" = "invoke list-files"
"search" = "invoke search"
"links" = "invoke links"

[dev-packages]

[packages]
clipboard = "*"
tabulate = "*"
python-levenshtein = "*"
invoke = "*"
63 changes: 59 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,68 @@
# nextcloud-link-generator

## Installation
Simply run ```python3 setup.py``` to install all needed libraries and create config.py. If you don't want your nextcloud password saved in config.py just skip the password question by hitting the enter key.
If you didn't save your passwort during setup you will be asked at running the script.

Install dependencies with `pipenv install`.

After installing you have to create a `config.py` file.
This can be done by running `pipenv run login --host=<url of the nextcloud> --base-dir=<directory to search for in nextcloud>`.
See also `config.py.example`.

## Usage
Run ```python3 main.py <exam1> <exam2> <exam n>``` with the exams you want as commandline arguments. If there are spaces in the name, replace them with a minus (```-```)

When using the cli the content of the directory is cached in the file `cache.json`.
This should make searches very fast.
The cache is refreshed if the `cache.json` is deleted or if the directory was changed or if the cache is not from the same date as today.
The cache can also be completely bypassed (and will not be updated on disk) by using the `--no-cache` flag.

List all exams:

```bash
pipenv run list-files
# without on disk cache
pipenv run list-files --no-cache
```

Search for exams:

```bash
# for fuzzy search
pipenv run search --lectures="exam1,exam2,exam 3"
# or for more exact matches
pipenv run search --exact --lectures="exam1,exam2,exam 3"
# to search without using the on disk cache
pipenv run search --no-cache --lectures="exam1,exam2,exam 3"
```

Generate links for exams:

```bash
# for fuzzy search
pipenv run links --lectures="exam1,exam2,exam 3"
# or for more exact matches
pipenv run links --exact --lectures="exam1,exam2,exam 3"
# to generate links without using the on disk cache
pipenv run links --no-cache --lectures="exam1,exam2,exam 3"
```

Get help for the commands:

```bash
# list all commands
pipenv run list
# get help for a specific command
pipenv run help <command-name>
# example:
pipenv run help search
```

Note: `pipenv run` can be replaced by `invoke` if inside the python venv.


## Used libraries

- https://github.com/owncloud/pyocclient at release 0.4
- python-Levenshtein
- tabulate
- clipboard
- clipboard
- invoke (only for cli)
130 changes: 130 additions & 0 deletions tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from invoke import task, context
from pathlib import Path
from urllib.request import urlopen, Request, urljoin, urlparse, urlunparse
from urllib.error import HTTPError
from json import loads
from time import sleep, time
import webbrowser


@task(help={
'host': 'The url of the nextcloud server.',
'base-dir': 'The path to search for directories in the nextcloud server. (Can be changed later in config.py)',
})
def login(c, host, base_dir='/'):
'''Login with the login flow v2 of nextcloud to get an app password.

This will open a browser tab for you to login to.
The app password will be written in config.py.
'''
url = urlparse(host)
if (url.scheme is not None) and (url.scheme != 'https'):
print('The connection to {} does not use the secure https protocol. Aborting login.'.format(url))

# generate loginflow url
nextcloud_host_url = urlunparse(('https', url.netloc, url.path, None, None, None))
login_flow_url = urljoin(host, '/index.php/login/v2')

# send request
req = Request(url=login_flow_url, method='POST')
req.add_header('User-Agent', 'nextcloud-link-generator')
response = None
with urlopen(req) as res:
if res.status != 200:
print("Could not start login flow!")
return
response = loads('\n'.join(map(lambda s: s.decode(), res.readlines())))
print(response)

webbrowser.open_new_tab(response['login'])

poll_data = 'token=' + response['poll']['token']
poll_req = Request(url=response['poll']['endpoint'], data=poll_data.encode(), method='POST')

poll_response = None
start = time()
while (poll_response is None) and ((time() - start) < 300):
try:
with urlopen(poll_req) as res:
if res.status == 200:
poll_response = loads('\n'.join(map(lambda s: s.decode(), res.readlines())))
break
else:
print('Something went wrong with the login flow.')
return
except HTTPError as e:
status = e.getcode()
if status == 404:
sleep(1)
else:
print('Something went wrong with the login flow.')
return
else:
print('Login took too long. Try again and be faster next time.')
return

if poll_response is None:
print('Could not read app password!')
return

print(poll_response)

with open("config.py", 'w') as outfile:
outfile.write("user = '{}'\n".format(poll_response['loginName']))
outfile.write("password = '{}'\n\n".format(poll_response['appPassword']))
outfile.write("url = '{}'\n\n".format(poll_response['server']))
outfile.write("base_dir = '{}'\n\n".format(base_dir))


@task(help={
'no-cache': 'Bypass the cache and load everything from the server.',
})
def list_files(c, no_cache=False):
'''List all directories in the basedir.'''
import config
from utilities import Nextcloud
nc = Nextcloud(config.url, config.user, config.password, config.base_dir)
for f in nc.get_filenames_with_path(no_cache=no_cache):
print(f['filename'], f['path'])


@task(help={
'lectures': 'A list of lecture names to search for. Seperate lectures with ",".',
'exact': 'Only include exact substring matches (still ignores character case).',
'no-cache': 'Bypass the cache and load everything from the server.',
})
def search(c, lectures, exact=False, no_cache=False):
'''Search for directories in the basedir.'''
import config
from utilities import Nextcloud
nc = Nextcloud(config.url, config.user, config.password, config.base_dir)
for f in nc.get_files_for_lectures(lectures.split(','), exact_matches=exact, no_cache=no_cache):
print(f['searchableFilename'], f['filename'], f['path'], sep='\t')

@task(help={
'lectures': 'A list of lecture names to search for. Seperate lectures with ",".',
'expiry': 'Nr. of days until links expire.',
'exact': 'Only include exact substring matches (still ignores character case).',
'no-cache': 'Bypass the cache and load everything from the server.',
})
def links(c, lectures, exact=False, no_cache=False, expiry=7):
'''Generate share links for directories in the basedir.'''
import config
from utilities import Helper
from tabulate import tabulate
from utilities import Nextcloud
nc = Nextcloud(config.url, config.user, config.password, config.base_dir)
files = nc.get_files_for_lectures(lectures.split(','), exact_matches=exact, no_cache=no_cache)
links = nc.get_links_for_files(files, link_expire_in_days=expiry)
clipboard = ''
table_content = []
for exam, link in links.items():
clipboard += '{exam}: {link}\n'.format(exam=exam, link=link)
table_content.append([exam, link])
table_content = sorted(table_content, key=lambda x: x[0])
try:
Helper.toClipboard(clipboard)
except Exception:
print(clipboard)
header = ["Exam", "Link"]
print(tabulate(table_content, headers=header))
Loading