deluge/setup.py
tbkizle 540d557cb2
[Common] Add is_interface to validate network interfaces
Libtorrent now supports interface names instead of just IP address so
add new common functions to validate user input.

* Added is_interface that will verify if a libtorrent interface of name
or IP address.
* Added is_interface_name to verify that the name supplied is a valid
network interface name in the operating system.
  On Windows sock.if_nameindex() is only supported on 3.8+ and does not
return a uuid (required by libtorrent) so use ifaddr package. Using git
commit version for ifaddr due to adapter name decode bug in v0.1.7.
On other OSes attempt to use stdlib and fallback to ifaddr if installed
otherwiser return True.
* Added tests for is_interface & is_interface_name
* Updated UIs with change from address to interface
* Updated is_ipv6 and is_ipv4 to used inet_pton; now supported on
Windows.

Ref: https://github.com/pydron/ifaddr/pull/32
Closes: https://github.com/deluge-torrent/deluge/pull/338
2022-01-30 16:13:27 +00:00

605 lines
18 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import glob
import os
import platform
import sys
from distutils.command.build import build as _build
from distutils.command.clean import clean as _clean
from distutils.command.install_data import install_data as _install_data
from shutil import rmtree, which
from setuptools import Command, find_packages, setup
from setuptools.command.test import test as _test
import msgfmt
from version import get_version
try:
from sphinx.setup_command import BuildDoc
except ImportError:
class BuildDoc:
pass
def windows_check():
return platform.system() in ('Windows', 'Microsoft')
def osx_check():
return platform.system() == 'Darwin'
desktop_data = 'deluge/ui/data/share/applications/deluge.desktop'
appdata_data = 'deluge/ui/data/share/appdata/deluge.appdata.xml'
# Variables for setuptools.setup
_package_data = {}
_exclude_package_data = {}
_entry_points = {'console_scripts': [], 'gui_scripts': [], 'deluge.ui': []}
_data_files = []
_version = get_version(prefix='deluge-', suffix='.dev0')
class PyTest(_test):
def initialize_options(self):
_test.initialize_options(self)
self.pytest_args = []
def finalize_options(self):
_test.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
import pytest
errcode = pytest.main(self.test_args)
sys.exit(errcode)
class CleanDocs(Command):
description = 'Clean the documentation build and module rst files'
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
docs_build = 'docs/build'
print(f'Deleting {docs_build}')
try:
rmtree(docs_build)
except OSError:
pass
for module in glob.glob('docs/source/modules/deluge*.rst'):
os.remove(module)
class BuildWebUI(Command):
description = 'Minify WebUI files'
user_options = []
JS_DIR = os.path.join('deluge', 'ui', 'web', 'js')
JS_SRC_DIRS = ('deluge-all', os.path.join('extjs', 'ext-extensions'))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
js_basedir = os.path.join(os.path.dirname(__file__), self.JS_DIR)
try:
from minify_web_js import minify_js_dir
import_error = ''
except ImportError as err:
import_error = err
for js_src_dir in self.JS_SRC_DIRS:
source_dir = os.path.join(js_basedir, js_src_dir)
try:
minify_js_dir(source_dir)
except NameError:
js_file = source_dir + '.js'
if os.path.isfile(js_file):
print(
'Unable to minify but found existing minified: {}'.format(
js_file
)
)
else:
# Unable to minify and no existing minified file found so exiting.
print('Import error: %s' % import_error)
sys.exit(1)
# Create the gettext.js file for translations.
try:
from gen_web_gettext import create_gettext_js
except ImportError:
pass
else:
deluge_all_path = os.path.join(js_basedir, self.JS_SRC_DIRS[0])
print('Creating WebUI translation file: %s/gettext.js' % deluge_all_path)
create_gettext_js(deluge_all_path)
class CleanWebUI(Command):
description = 'Clean the documentation build and rst files'
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
js_basedir = os.path.join(os.path.dirname(__file__), BuildWebUI.JS_DIR)
# Remove files generated by minify script.
for js_src_dir in BuildWebUI.JS_SRC_DIRS:
for file_type in ('.js', '-debug.js'):
js_file = os.path.join(js_basedir, js_src_dir + file_type)
print(f'Deleting {js_file}')
try:
os.remove(js_file)
except OSError:
pass
# Remove generated gettext.js
js_file = os.path.join(js_basedir, 'gettext.js')
print(f'Deleting {js_file}')
try:
os.remove(js_file)
except OSError:
pass
class BuildTranslations(Command):
description = 'Compile .po files into .mo files & create .desktop file'
user_options = [
('build-lib', None, 'lib build folder'),
('develop', 'D', 'Compile translations in develop mode (deluge/i18n)'),
]
boolean_options = ['develop']
def initialize_options(self):
self.build_lib = None
self.develop = False
def finalize_options(self):
self.set_undefined_options('build', ('build_lib', 'build_lib'))
def run(self):
po_dir = os.path.join(os.path.dirname(__file__), 'deluge', 'i18n')
if self.develop:
basedir = po_dir
else:
basedir = os.path.join(self.build_lib, 'deluge', 'i18n')
intltool_merge = 'intltool-merge'
if not windows_check() and which(intltool_merge):
intltool_merge_opts = '--utf8 --quiet'
for data_file in (desktop_data, appdata_data):
# creates the translated file from .in file.
in_file = data_file + '.in'
if 'xml' in data_file:
intltool_merge_opts += ' --xml-style'
elif 'desktop' in data_file:
intltool_merge_opts += ' --desktop-style'
print('Creating file: %s' % data_file)
os.system(
'C_ALL=C '
+ '%s '
* 5
% (intltool_merge, intltool_merge_opts, po_dir, in_file, data_file)
)
print('Compiling po files from %s...' % po_dir)
for path, names, filenames in os.walk(po_dir):
for f in filenames:
upto_date = False
if f.endswith('.po'):
lang = f[: len(f) - 3]
src = os.path.join(path, f)
dest_path = os.path.join(basedir, lang, 'LC_MESSAGES')
dest = os.path.join(dest_path, 'deluge.mo')
if not os.path.exists(dest_path):
os.makedirs(dest_path)
if not os.path.exists(dest):
sys.stdout.write('%s, ' % lang)
sys.stdout.flush()
msgfmt.make(src, dest)
else:
src_mtime = os.stat(src)[8]
dest_mtime = os.stat(dest)[8]
if src_mtime > dest_mtime:
sys.stdout.write('%s, ' % lang)
sys.stdout.flush()
msgfmt.make(src, dest)
else:
upto_date = True
if upto_date:
sys.stdout.write(' po files already up to date. ')
sys.stdout.write('\b\b \nFinished compiling translation files. \n')
class CleanTranslations(Command):
description = 'Cleans translations files.'
user_options = [
('all', 'a', 'Remove all build output, not just temporary by-products')
]
boolean_options = ['all']
def initialize_options(self):
self.all = None
def finalize_options(self):
self.set_undefined_options('clean', ('all', 'all'))
def run(self):
for path in (desktop_data, appdata_data):
if os.path.isfile(path):
print('Deleting %s' % path)
os.remove(path)
class BuildPlugins(Command):
description = 'Build plugins into .eggs'
user_options = [
('install-dir=', None, 'develop install folder'),
('develop', 'D', 'Compile plugins in develop mode'),
]
boolean_options = ['develop']
def initialize_options(self):
self.install_dir = None
self.develop = False
def finalize_options(self):
pass
def run(self):
# Build the plugin eggs
plugin_path = 'deluge/plugins/*'
for path in glob.glob(plugin_path):
if os.path.exists(os.path.join(path, 'setup.py')):
if self.develop and self.install_dir:
os.system(
'cd '
+ path
+ '&& '
+ sys.executable
+ ' setup.py develop --install-dir=%s' % self.install_dir
)
elif self.develop:
os.system(
'cd ' + path + '&& ' + sys.executable + ' setup.py develop'
)
else:
os.system(
'cd '
+ path
+ '&& '
+ sys.executable
+ ' setup.py bdist_egg -d ..'
)
class CleanPlugins(Command):
description = 'Cleans the plugin folders'
user_options = [
('all', 'a', 'Remove all build output, not just temporary by-products')
]
boolean_options = ['all']
def initialize_options(self):
self.all = None
def finalize_options(self):
self.set_undefined_options('clean', ('all', 'all'))
def run(self):
print('Cleaning the plugin\'s folders...')
plugin_path = 'deluge/plugins/*'
for path in glob.glob(plugin_path):
if os.path.exists(os.path.join(path, 'setup.py')):
c = 'cd ' + path + ' && ' + sys.executable + ' setup.py clean'
if self.all:
c += ' -a'
print('Calling \'%s\'' % c)
os.system(c)
# Delete the .eggs
if path[-4:] == '.egg':
print('Deleting egg file "%s"' % path)
os.remove(path)
# Delete the .egg-link
if path[-9:] == '.egg-link':
print('Deleting egg link "%s"' % path)
os.remove(path)
egg_info_dir_path = 'deluge/plugins/*/*.egg-info'
for path in glob.glob(egg_info_dir_path):
# Delete the .egg-info's directories
if path[-9:] == '.egg-info':
print('Deleting %s' % path)
for fpath in os.listdir(path):
os.remove(os.path.join(path, fpath))
os.removedirs(path)
class EggInfoPlugins(Command):
description = 'Create .egg-info directories for plugins'
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
# Build the plugin eggs
plugin_path = 'deluge/plugins/*'
for path in glob.glob(plugin_path):
if os.path.exists(os.path.join(path, 'setup.py')):
os.system('cd ' + path + '&& ' + sys.executable + ' setup.py egg_info')
class Build(_build):
sub_commands = [
('build_webui', None),
('build_trans', None),
('build_plugins', None),
] + _build.sub_commands
def run(self):
# Run all sub-commands (at least those that need to be run).
_build.run(self)
try:
from deluge._libtorrent import LT_VERSION
print(f'Info: Found libtorrent ({LT_VERSION}) installed.')
except ImportError as ex:
print('Warning: libtorrent (libtorrent-rasterbar) not found: %s' % ex)
class InstallData(_install_data):
"""Custom class to fix `setup install` copying data files to incorrect location. (Bug #1389)"""
def finalize_options(self):
self.install_dir = None
self.set_undefined_options(
'install',
('install_data', 'install_dir'),
('root', 'root'),
('force', 'force'),
)
def run(self):
_install_data.run(self)
class Clean(_clean):
sub_commands = _clean.sub_commands + [
('clean_plugins', None),
('clean_trans', None),
('clean_webui', None),
]
def run(self):
# Remove deluge egg-info.
root_egg_info_dir_path = 'deluge*.egg-info'
for path in glob.glob(root_egg_info_dir_path):
print('Deleting %s' % path)
for fpath in os.listdir(path):
os.remove(os.path.join(path, fpath))
os.removedirs(path)
# Run all sub-commands (at least those that need to be run)
for cmd_name in self.get_sub_commands():
self.run_command(cmd_name)
_clean.run(self)
cmdclass = {
'build': Build,
'build_webui': BuildWebUI,
'build_trans': BuildTranslations,
'build_plugins': BuildPlugins,
'build_docs': BuildDoc,
'spellcheck_docs': BuildDoc,
'install_data': InstallData,
'clean_plugins': CleanPlugins,
'clean_trans': CleanTranslations,
'clean_docs': CleanDocs,
'clean_webui': CleanWebUI,
'clean': Clean,
'egg_info_plugins': EggInfoPlugins,
'test': PyTest,
}
if not windows_check() and not osx_check():
for icon_path in glob.glob('deluge/ui/data/icons/hicolor/*x*'):
size = os.path.basename(icon_path)
icons = glob.glob(os.path.join(icon_path, 'apps', 'deluge*.png'))
_data_files.append((f'share/icons/hicolor/{size}/apps', icons))
_data_files.extend(
[
(
'share/icons/hicolor/scalable/apps',
['deluge/ui/data/icons/hicolor/scalable/apps/deluge.svg'],
),
('share/pixmaps', ['deluge/ui/data/pixmaps/deluge.png']),
(
'share/man/man1',
[
'docs/man/deluge.1',
'docs/man/deluged.1',
'docs/man/deluge-gtk.1',
'docs/man/deluge-web.1',
'docs/man/deluge-console.1',
],
),
]
)
if os.path.isfile(desktop_data):
_data_files.append(('share/applications', [desktop_data]))
if os.path.isfile(appdata_data):
_data_files.append(('share/appdata', [appdata_data]))
_entry_points['console_scripts'] = [
'deluge-console = deluge.ui.console:start',
'deluge-web = deluge.ui.web:start',
'deluged = deluge.core.daemon_entry:start_daemon',
]
if windows_check():
_entry_points['console_scripts'].extend(
[
'deluge-debug = deluge.ui.ui_entry:start_ui',
'deluge-web-debug = deluge.ui.web:start',
'deluged-debug = deluge.core.daemon_entry:start_daemon',
]
)
_entry_points['gui_scripts'] = [
'deluge = deluge.ui.ui_entry:start_ui',
'deluge-gtk = deluge.ui.gtk3:start',
]
_entry_points['deluge.ui'] = [
'console = deluge.ui.console:Console',
'web = deluge.ui.web:Web',
'gtk = deluge.ui.gtk3:Gtk',
]
_package_data['deluge'] = [
'ui/data/pixmaps/*.png',
'ui/data/pixmaps/*.svg',
'ui/data/pixmaps/*.ico',
'ui/data/pixmaps/*.gif',
'ui/data/pixmaps/flags/*.png',
'plugins/*.egg',
'i18n/*/LC_MESSAGES/*.mo',
]
_package_data['deluge.ui.web'] = [
'index.html',
'css/*.css',
'icons/*.png',
'images/*.gif',
'images/*.png',
'js/*.js',
'js/extjs/*.js',
'render/*.html',
'themes/css/*.css',
'themes/images/*/*.gif',
'themes/images/*/*.png',
'themes/images/*/*/*.gif',
'themes/images/*/*/*.png',
]
_package_data['deluge.ui.gtk3'] = ['glade/*.ui']
setup_requires = ['setuptools', 'wheel']
install_requires = [
'twisted[tls]>=17.1',
# Add pyasn1 for setuptools workaround:
# https://github.com/pypa/setuptools/issues/1510
'pyasn1',
'rencode',
'pyopenssl',
'pyxdg',
'mako',
'setuptools',
"pywin32; sys_platform == 'win32'",
"certifi; sys_platform == 'win32'",
'zope.interface',
]
extras_require = {
'all': [
'setproctitle',
'pillow',
'chardet',
'ifaddr',
]
}
# Main setup
setup(
name='deluge',
version=_version,
fullname='Deluge BitTorrent Client',
description='BitTorrent Client',
author='Deluge Team',
maintainer='Calum Lind',
maintainer_email='calumlind+deluge@gmail.com',
keywords='torrent bittorrent p2p fileshare filesharing',
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
url='https://deluge-torrent.org',
project_urls={
'GitHub (mirror)': 'https://github.com/deluge-torrent/deluge',
'Sourcecode': 'http://git.deluge-torrent.org/deluge',
'Issues': 'https://dev.deluge-torrent.org/report/1',
'Discussion': 'https://forum.deluge-torrent.org',
'Documentation': 'https://deluge.readthedocs.io',
},
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Environment :: Web Environment',
'Environment :: X11 Applications :: GTK',
'Framework :: Twisted',
'Intended Audience :: End Users/Desktop',
(
'License :: OSI Approved :: '
'GNU General Public License v3 or later (GPLv3+)'
),
'Programming Language :: Python',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Topic :: Internet',
],
python_requires='>=3.6',
license='GPLv3+',
cmdclass=cmdclass,
setup_requires=setup_requires,
install_requires=install_requires,
extras_require=extras_require,
data_files=_data_files,
package_data=_package_data,
exclude_package_data=_exclude_package_data,
packages=find_packages(exclude=['deluge.plugins.*', 'deluge.tests']),
entry_points=_entry_points,
)