49 |
--------------------------------------------------------------------------------
/launcher/launcher_entry.py:
--------------------------------------------------------------------------------
1 | # Drakkar-Software OctoBot-Launcher
2 | # Copyright (c) Drakkar-Software, All rights reserved.
3 | #
4 | # This library is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library.
16 |
17 | import argparse
18 | import logging
19 | import sys
20 | import webbrowser
21 |
22 | from launcher import VERSION
23 | from launcher.tools import environment
24 | from launcher.tools.environment import ensure_minimum_environment
25 |
26 |
27 | def launcher(args=sys.argv[1:]):
28 | logging.basicConfig(level=logging.INFO)
29 |
30 | parser = argparse.ArgumentParser(description='OctoBot - Launcher')
31 | parser.add_argument('-e', '--export_logs', help="export Octobot's last logs",
32 | action='store_true')
33 | parser.add_argument('-v', '--version', help='show OctoBot Launcher current version',
34 | action='store_true')
35 | parser.add_argument('-u', '--update', help='update OctoBot with the latest version available',
36 | action='store_true')
37 | parser.add_argument('-s', '--start', help='Start OctoBot. OctoBot starting options can be added after '
38 | '-s or --start. Examples: "-s no t" will start OctoBot '
39 | 'with "ng" option and "t" that will use telegram interface, without gui.',
40 | nargs='*')
41 | parser.add_argument('-nw', '--no_web', help="Without web server", action='store_true')
42 |
43 | args = parser.parse_args(args)
44 |
45 | start_launcher(args)
46 |
47 |
48 | def start_launcher(args):
49 | if args.version:
50 | print(VERSION)
51 | else:
52 | ensure_minimum_environment()
53 | from launcher.app.launcher_app import LauncherApp
54 | if not args.no_web:
55 | try:
56 | app = LauncherApp()
57 | webbrowser.open(app.get_web_server_url())
58 | except Exception as e:
59 | logging.error(f"Can't start gui, please try command line interface (use --help).\n{e}")
60 |
61 | if args.update:
62 | environment.install_bot(force_package=False)
63 | elif args.start:
64 | LauncherApp().start_bot_handler([f"-{arg}" for arg in args.start] if args.start else None)
65 | elif args.export_logs:
66 | pass
67 | else:
68 | pass
69 |
--------------------------------------------------------------------------------
/launcher/static/js/commons.js:
--------------------------------------------------------------------------------
1 | function load_template(template_url){
2 | const template_location = $("div[template='"+template_url+"']");
3 | $.ajax({
4 | url: template_url,
5 | beforeSend: function() {
6 | template_location.html("
66 |
67 | {% include "footer.html" %}
68 |
69 | {% block additional_scripts %}
70 | {% endblock additional_scripts %}
71 |
72 |
73 |
--------------------------------------------------------------------------------
/launcher/__init__.py:
--------------------------------------------------------------------------------
1 | # Drakkar-Software OctoBot-Launcher
2 | # Copyright (c) Drakkar-Software, All rights reserved.
3 | #
4 | # This library is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library.
16 | from enum import Enum
17 |
18 | import flask
19 |
20 | PROJECT_NAME = "OctoBot-Launcher"
21 | OCTOBOT_BINARY = "OctoBot-Binary"
22 | OCTOBOT_NAME = "OctoBot"
23 | SHORT_VERSION = "2.0.4"
24 | PATCH_VERSION = "" # patch : pX
25 | VERSION_DEV_PHASE = "" # alpha : a / beta : b / release candidate : rc
26 | VERSION_PHASE = "" # XX
27 | VERSION = f"{SHORT_VERSION}{VERSION_DEV_PHASE}{VERSION_PHASE}"
28 | LONG_VERSION = f"{SHORT_VERSION}{PATCH_VERSION}{VERSION_DEV_PHASE}{VERSION_PHASE}"
29 |
30 | FORCE_BINARY = True
31 | REDUCE_GITHUB_REQUESTS = False
32 |
33 | OCTOBOT_REFERENCE_BRANCH = "master"
34 | OCTOBOT_LAUNCHER_VERSION_BRANCH = "master"
35 | OCTOBOT_DEV_PHASE = "beta"
36 |
37 | DEFAULT_CONFIG_FILE = "config/default_config.json"
38 | LOGGING_CONFIG_FILE = "config/logging_config.ini"
39 | STRATEGY_OPTIMIZER_DATA_FOLDER = "tests/static"
40 |
41 | GITHUB_RAW_CONTENT_URL = "https://raw.githubusercontent.com"
42 | GITHUB_API_CONTENT_URL = "https://api.github.com"
43 | GITHUB_BASE_URL = "https://github.com"
44 | GITHUB_ORGANISATION = "Drakkar-Software"
45 | OCTOBOT_GITHUB_REPOSITORY = f"{GITHUB_ORGANISATION}/{OCTOBOT_NAME}"
46 | OCTOBOT_BINARY_GITHUB_REPOSITORY = f"{GITHUB_ORGANISATION}/{OCTOBOT_BINARY}"
47 | LAUNCHER_GITHUB_REPOSITORY = f"{GITHUB_ORGANISATION}/{PROJECT_NAME}"
48 | GITHUB_URL = f"{GITHUB_BASE_URL}/{OCTOBOT_GITHUB_REPOSITORY}"
49 |
50 | LAUNCHER_PATH = "launcher"
51 |
52 | CONFIG_FILE = "user/config.json"
53 | CONFIG_FILE_SCHEMA_NAME = "config_schema.json"
54 |
55 | REPOSITORY_BRANCH = "master"
56 |
57 | TENTACLES_PATH = "tentacles"
58 | CONFIG_FILE_SCHEMA_WITH_PATH = f"config/{CONFIG_FILE_SCHEMA_NAME}"
59 | CONFIG_DEFAULT_EVALUATOR_FILE = "config/default_evaluator_config.json"
60 | CONFIG_DEFAULT_TRADING_FILE = "config/default_trading_config.json"
61 |
62 | CONFIG_INTERFACES = "interfaces"
63 | CONFIG_INTERFACES_WEB = "web"
64 |
65 | OCTOBOT_CONFIG_FILE = "config.json"
66 | CONFIG_CATEGORY_SERVICES = "services"
67 | CONFIG_WEB = "web"
68 | CONFIG_WEB_PORT = "port"
69 |
70 | OCTOBOT_BACKGROUND_IMAGE = "static/img/octobot.png"
71 | OCTOBOT_ICON = "static/favicon.ico"
72 |
73 | DEFAULT_SERVER_IP = '0.0.0.0'
74 | DEFAULT_SERVER_PORT = 5010 # prevent conflicts with OctoBot web interface
75 |
76 | OCTOBOT_DEFAULT_SERVER_PORT = 5001
77 | OCTOBOT_API_MAX_RETRIES = 20
78 |
79 | server_instance = flask.Flask(__name__)
80 | launcher_instance = None
81 | bot_instance = None
82 | processing = 0
83 |
84 | WINDOWS_OS_NAME = "nt"
85 | MAC_OS_NAME = "mac"
86 | LINUX_OS_NAME = "posix"
87 |
88 |
89 | class DeliveryPlatformsName(Enum):
90 | WINDOWS = "windows"
91 | LINUX = "linux"
92 | MAC = "osx"
93 |
94 |
95 | def get_launcher_instance():
96 | global launcher_instance
97 | return launcher_instance
98 |
99 |
100 | def inc_progress(inc_size, to_min=False, to_max=False):
101 | global processing
102 | if to_max:
103 | processing = 100
104 | elif to_min:
105 | processing = 0
106 | else:
107 | processing += inc_size
108 |
109 |
110 | def load_routes():
111 | from launcher.app import app_controller
112 |
--------------------------------------------------------------------------------
/launcher/app/launcher_app.py:
--------------------------------------------------------------------------------
1 | # Drakkar-Software OctoBot-Launcher
2 | # Copyright (c) Drakkar-Software, All rights reserved.
3 | #
4 | # This library is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library.
16 |
17 | import os
18 | import signal
19 | import subprocess
20 | import sys
21 | from threading import Thread
22 |
23 | import launcher
24 | from entrypoint import update_launcher
25 | from launcher.tools import environment, executor
26 | from launcher.app.web_app import WebApp
27 | from launcher.tools.version import OctoBotVersion
28 |
29 |
30 | class LauncherApp(WebApp):
31 | def __init__(self):
32 | launcher.launcher_instance = self
33 |
34 | super().__init__()
35 | self.start_app()
36 |
37 | def update_bot_handler(self):
38 | thread = Thread(target=self.update_bot, args=(self,))
39 | thread.start()
40 |
41 | def update_package_handler(self):
42 | thread = Thread(target=self.update_bot, args=(self,))
43 | thread.start()
44 |
45 | @staticmethod
46 | def launcher_start_args():
47 | # prevent binary to add self as first argument
48 | return sys.argv[0] if sys.argv[0].endswith(".py") else ""
49 |
50 | def update_launcher_handler(self):
51 | launcher_process = subprocess.Popen(
52 | [sys.executable, self.launcher_start_args(), "--update_launcher"]
53 | if self.launcher_start_args() else [sys.executable, "--update_launcher"]
54 | )
55 |
56 | if launcher_process:
57 | launcher_process.wait()
58 |
59 | self.restart_launcher()
60 |
61 | def restart_launcher(self):
62 | self.prepare_stop()
63 | new_launcher_process = subprocess.Popen([sys.executable, self.launcher_start_args()]
64 | if self.launcher_start_args() else [sys.executable])
65 |
66 | if new_launcher_process:
67 | new_launcher_process.wait()
68 | self.stop()
69 |
70 | def stop_launcher(self):
71 | self.prepare_stop()
72 | self.stop()
73 |
74 | @staticmethod
75 | def start_bot_handler(args=None):
76 | launcher.bot_instance = executor.execute_command_on_detached_binary(OctoBotVersion().get_local_binary(),
77 | commands=args)
78 | # if launcher.bot_instance:
79 | # launcher.bot_instance.wait()
80 |
81 | @staticmethod
82 | def is_bot_alive():
83 | return launcher.bot_instance is not None and launcher.bot_instance.poll() is None
84 |
85 | @staticmethod
86 | def stop_bot():
87 | if launcher.bot_instance:
88 | os.kill(launcher.bot_instance.pid, signal.SIGINT)
89 | launcher.bot_instance.terminate()
90 | launcher.bot_instance = None
91 |
92 | @staticmethod
93 | def update_bot(force_package=False):
94 | environment.install_bot(force_package=force_package)
95 |
96 | @staticmethod
97 | def update_launcher():
98 | update_launcher()
99 |
100 | def export_logs(self):
101 | # TODO
102 | pass
103 |
104 | @staticmethod
105 | def close():
106 | os._exit(0)
107 |
108 | def start_app(self):
109 | self.start()
110 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | notifications:
2 | email: false
3 | sudo: enabled
4 | os: linux
5 | cache: pip
6 | python: 3.7-dev
7 |
8 | matrix:
9 | include:
10 | - os: osx
11 | osx_image: xcode8.3
12 | language: generic
13 | before_cache:
14 | - brew cleanup
15 | cache:
16 | directories:
17 | - $HOME/Library/Caches/Homebrew
18 | before_install:
19 | - brew update || travis_terminate 1;
20 | - brew upgrade python || brew install python || travis_terminate 1;
21 | - python3 -m venv venv || travis_terminate 1;
22 | - source venv/bin/activate;
23 | - os: linux
24 | language: python
25 |
26 | install:
27 | - python3 -m pip install -r requirements.txt -r dev_requirements.txt
28 |
29 | script:
30 | - pytest
31 |
32 | before_deploy:
33 | - cp -r launcher launcher_save
34 | - rm -rf launcher/static/* launcher/templates/* launcher/app launcher/tools launcher/*.py
35 | - pyinstaller bin/launcher.spec
36 | - mv dist/launcher_app ./launcher_app_$TRAVIS_OS_NAME && rm -rf dist/
37 | - rm -rf launcher
38 | - mv launcher_save launcher
39 | - RELEASE_SHASUM=$(openssl sha1 ./launcher_app_$TRAVIS_OS_NAME)
40 | - if [[ $TRAVIS_OS_NAME == 'linux' ]]; then python3 setup.py sdist bdist_wheel && python3 -m twine check dist/*; fi
41 |
42 | deploy:
43 | - provider: releases
44 | api_key:
45 | secure: cQaezwO+v0d3NJu2oY0+7e3JngZkSju+dXTUimIyTfZGDUaS7yCeGW5Q0Zb+LolEXYuMWdcv0VaIKZjSL5Yb4ROaTk9qg5g1jnHk8ntzxlemKAk/ahmsIe+MSx14g1PAGYTJBGftWTAqzZ/ZWMBQqSgL2zhxI5gLprnaDfRA4z1mZXNCGo8MASTI9mf+jrIt9rQsJ2pjldTTP7G5ofJfqqg3JLafcWQl6vg5HwaisFRlrtfyWurZIhA8y95Qrin51Kyj2jlxABVqA0mgC6cf2SpvYlID2sG/JzH2SWWQ1JO4wt832aACQAr3trDr/+Ko5ivJJXYqetDCvgTwd4V+4CmO5YyCcPZAOmLJVnY1I3svhWXR/XQqqK4hfMN42KC2NlHVCXvD/gYpbbJKwcAeGQI28tDjV9yZe8ZEu095pFb8AUPJVd40vNTAcYgScfIWneJ7NR0I/LK/itUmjeIOU6k+f1X94yKgNlZeAjAtBc/45Am/He1uCMVI4RD8yqlLBUqh9ofOvdzkNBOmkJ4xieJVsyrR/+QjNnOlFgt+ZDAKLhOr3RLqzy4FS7C+RrJIb0HZfSjnruj5HOdLEOubDdf3s2sbhWyl/DVJ/m1D/kLSitgql9ME3RYmTHadNRSw0u3jF5nATuin6iNZdG7VroiIacsIUlzbkx2UO/kGY1g=
46 | file: ./launcher_app_$TRAVIS_OS_NAME
47 | skip_cleanup: true
48 | name: Release - $TRAVIS_TAG
49 | body: $RELEASE_SHASUM
50 | on:
51 | repo: Drakkar-Software/OctoBot-Launcher
52 | branch: master
53 | tags: true
54 | - provider: pypi
55 | user:
56 | secure: gCnxIfOj9GYRu3rAmoaE73VeELUzINK1ZseAYo4RY2lzJ+QHNiXcaQuHrj/CyyaTcrKOeHzvCtA5J5e3WQqU29ul3JaJXSIdWSamxyx9H3YdoxFs9PjXQhDx3pE/3DLDasvzDRZNCux6oZ31WzL7qdaZr1eYNrg75uIRusLYvLu4hwG41w0OZ5YjnehK98Lx5YkiXYq+Gw5FChLrliN3NYI9HoM13XWQcdghatgg+jzXW3Yp2svL7n4kw54WCZ+et50dC74+mDEG5VGK6/LcQzVipTIppdcK3qpHBhL65ZIxC0IUSJkD/Vrb4s53nF7RLm/XiLrpHdWGQ20KGMIB0b8VTOUnuCEcayW7Q6ieU405r7h+hvr+Qn37v+EI7+aUfWTnz73ZWYJ3o3F94hNYQfaNBEY98NEWscIx4esPvjYsMxXIb/LvxjMQAZP1SWwiQ+DFgxTZNUXgk7cpaJkTzgBim6t5mwXnoQLRz/kC+B7CYg3wPxDJ8kwB7hJQzbkgul1ca83qBZHOSnpkESfkyjpVim1NZQkQH1D3T5Izp7i88epScV+0cBOtqOPiL9bPDZ4wve3/D1v2IsqqMAyiMTZF/QFa8hFvXL09QKKxoufl8/vuP7wUj1ebu0o+7qPhXS6gI6us4KUYYQdNMqZg/xHzSIiRAtFIlG8T+WpDcu8=
57 | password:
58 | secure: LDxZV2mJ6BB5bAt2XSf9AjWgUEJValfNHeiGZuH4WNsxgCQqOIRDhP1om/q8zL47c6iYHvV9N5h9xC0Nz9mIKVJxL8HWReJ8i4jYaE6dn+FKGNWZED5KJa0fTTsQUVdRhekU92fV8JMSgFKf8JI4kEofb4NNpgD3oC8zS16oG0CC4TZ90KWgunU5HJrV2m4tSw86wLJDehpPjnucan217oTuBTZxG8S+cNF7qVc0oSbJFOTW1jtGwFc6T9bLMF5CELRoChcpY3BhRGhu21LjCLwFc/BY0wNfTzlrY1sNlM1KorRvikIYWL8F0wHNCewztBORUIGgcYgvXj2I/Endrti4CdfcySHTMqn2uffv99gFinIftMy+F1E37cM7vRUY08na2jqH/3k2rrE/vKeLep1Lzk4LkaBG72Co0t509sORAFzQoIrVdnJPf5ZidJzEaivwi3ZG1/GkmXC5ERH5CBwWihHF4itdi0deSsmWKbHthJCEw3emqEhrbDAwwfdIV2MuwGmxvJ5oxjZBBSzpblO7b0LdHhoEqdeqbBs5BHVRwjmsNia86dxvzZH6lj7ylllAl6Z7XENynOUNUqgLhbQeDioR3EWLUTvXnGf3+gPieSPmD4F+YYfLzwIjRMWmu0yLX9N57DrkM/fAlq3l8AVnjX9TZFk7iVyuNkUfmYQ=
59 | skip_existing: true
60 | skip_cleanup: true
61 | distributions: "sdist bdist_wheel"
62 | on:
63 | repo: Drakkar-Software/OctoBot-Launcher
64 | branch: master
65 | tags: true
66 | condition: $TRAVIS_OS_NAME = 'linux'
67 |
--------------------------------------------------------------------------------
/launcher/static/css/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Drakkar-Software OctoBot
3 | * Copyright (c) Drakkar-Software, All rights reserved.
4 | *
5 | * This library is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3.0 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library.
17 | */
18 |
19 | html, body{
20 | height: 100%;
21 | min-height: 100%;
22 | }
23 |
24 | body:before {
25 | content: "";
26 | position: fixed;
27 | left: 0;
28 | right: 0;
29 | z-index: -1;
30 |
31 | background-image: url('../img/svg/octobot.svg');
32 |
33 | background-repeat: no-repeat;
34 | background-size: cover;
35 | width: 100%;
36 | height: 100%;
37 | opacity : 0.2;
38 |
39 | }
40 |
41 | /* Cards */
42 | .card-deck {
43 | display: flex;
44 | justify-content: flex-start;
45 | flex-flow: row wrap;
46 | align-items: stretch;
47 | }
48 | .card {
49 | display: block;
50 | flex-basis: 33.3%;
51 | .rounded-bottom !important;
52 | }
53 | .medium-size {
54 | max-width: 18rem;
55 | min-width: 12rem;
56 | }
57 | .small-size {
58 | max-width: 18rem;
59 | min-width: 12rem;
60 | max-height: 18rem;
61 | min-height: 14rem;
62 | }
63 | .very-small-size{
64 | max-width: 3rem;
65 | min-width: 2rem;
66 | max-height: 3rem;
67 | min-height: 2rem;
68 | }
69 |
70 | /* elegant-color white-text */
71 |
72 | /* Theme */
73 | /* Elegant */
74 | .card,
75 | .card-body,
76 | .card-text,
77 | .select2-selection__choice
78 | {
79 | background-color: #3E4551 !important;
80 | font-size: 16px !important;
81 | }
82 |
83 | .card,
84 | .card-body,
85 | .card-text,
86 | .select2 li span,
87 | .card-text{
88 | color: white !important;
89 | }
90 |
91 | .card .card,
92 | .card .card .card-body,
93 | .card .card .card-text,
94 | .card .card-deck .card,
95 | .card .card-deck .card .card-body,
96 | .card .card-deck .card .card-text{
97 | background-color: #4B515D !important;
98 | }
99 |
100 | .card .deck-container-modified {
101 | background-color: #FF8800 !important;
102 | -webkit-transition: all 0.5s ease;
103 | -moz-transition: all 0.5s ease;
104 | -o-transition: all 0.5s ease;
105 | transition: all 0.5s ease;
106 | }
107 |
108 | .card .card-modified,
109 | .card .card-modified .card-body,
110 | .card .card-modified .card-text,
111 | .card .card-deck .card-modified,
112 | .card .card-deck .card-modified .card-body,
113 | .card .card-deck .card-modified .card-text{
114 | background-color: #FF8800 !important;
115 | -webkit-transition: all 0.5s ease;
116 | -moz-transition: all 0.5s ease;
117 | -o-transition: all 0.5s ease;
118 | transition: all 0.5s ease;
119 | }
120 |
121 | .card .card-long,
122 | .card .card-long .card-body,
123 | .card .card-long .card-text,
124 | .card .card-deck .card-long,
125 | .card .card-deck .card-long .card-body,
126 | .card .card-deck .card-long .card-text{
127 | background-color: seagreen !important;
128 | -webkit-transition: all 0.5s ease;
129 | -moz-transition: all 0.5s ease;
130 | -o-transition: all 0.5s ease;
131 | transition: all 0.5s ease;
132 | }
133 |
134 | .card .card-short,
135 | .card .card-short .card-body,
136 | .card .card-short .card-text,
137 | .card .card-deck .card-short,
138 | .card .card-deck .card-short .card-body,
139 | .card .card-deck .card-short .card-text{
140 | background-color: darkred !important;
141 | -webkit-transition: all 0.5s ease;
142 | -moz-transition: all 0.5s ease;
143 | -o-transition: all 0.5s ease;
144 | transition: all 0.5s ease;
145 | }
146 |
147 | .card a:link {
148 | color: #33b5e5;
149 | }
150 |
151 | .card a:hover {
152 | color: #42a5f5 !important;
153 | }
154 |
155 | .modal-text {
156 | color: #4B515D;
157 | }
158 |
--------------------------------------------------------------------------------
/launcher/tools/version.py:
--------------------------------------------------------------------------------
1 | # Drakkar-Software OctoBot-Launcher
2 | # Copyright (c) Drakkar-Software, All rights reserved.
3 | #
4 | # This library is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library.
16 | import glob
17 | import logging
18 | import os
19 | import subprocess
20 | import sys
21 | from distutils.version import LooseVersion
22 | from subprocess import PIPE
23 |
24 | import pkg_resources
25 |
26 | from launcher import LINUX_OS_NAME, FORCE_BINARY, \
27 | WINDOWS_OS_NAME, MAC_OS_NAME, PROJECT_NAME, OCTOBOT_NAME, launcher_instance, inc_progress
28 | from launcher.tools import executor, BINARY_DOWNLOAD_PROGRESS_SIZE
29 |
30 |
31 | class Version:
32 | PROJECT = ""
33 | NOT_INSTALLED_VERSION = "not installed"
34 |
35 | def is_package_installed(self):
36 | try:
37 | pkg_resources.get_distribution(self.PROJECT)
38 | return True
39 | except (pkg_resources.DistributionNotFound, pkg_resources.RequirementParseError):
40 | return False
41 |
42 | def download_package(self):
43 | try:
44 | cmd = [sys.executable, "-m", "pip", "install", "-U", self.PROJECT]
45 | return subprocess.Popen(cmd, stdout=PIPE).stdout.read().rstrip().decode()
46 | except PermissionError as e:
47 | logging.error(f"Failed to update package : {e}")
48 | except FileNotFoundError as e:
49 | logging.error(f"Can't find a valid python executable : {e}")
50 |
51 | def get_local_binary(self, force_binary=False):
52 | binary = None
53 |
54 | if not force_binary and self.is_package_installed():
55 | binary = self.PROJECT
56 | else:
57 | try:
58 | # try to found in current folder binary
59 | if os.name == LINUX_OS_NAME:
60 | binary = f"./{next(iter(glob.glob(f'{self.PROJECT}*')))}"
61 |
62 | elif os.name == WINDOWS_OS_NAME:
63 | binary = next(iter(glob.glob(f'{self.PROJECT}*.exe')))
64 |
65 | elif os.name == MAC_OS_NAME:
66 | binary = f"./{next(iter(glob.glob(f'{self.PROJECT}*')))}"
67 | except StopIteration:
68 | binary = None
69 |
70 | return binary
71 |
72 | @staticmethod
73 | def is_binary_available(binary_path):
74 | if binary_path is None or not os.path.isfile(binary_path):
75 | return False
76 | return True
77 |
78 | def get_current_version(self, binary_path=None, force_binary=FORCE_BINARY):
79 | if not binary_path:
80 | binary_path = self.get_local_binary(force_binary=force_binary)
81 | if not self.is_binary_available(binary_path):
82 | return self.NOT_INSTALLED_VERSION
83 | return executor.execute_command_on_current_binary(binary_path, ["--version"]).split("\r\n")[0]
84 |
85 | def get_local_version_or_download(self, github_instance, binary_path,
86 | latest_release_data=None, force_binary=FORCE_BINARY):
87 | last_release_version = github_instance.get_current_server_version(latest_release_data=latest_release_data)
88 | current_version = self.get_current_version(binary_path, force_binary=force_binary)
89 |
90 | try:
91 | if current_version == self.NOT_INSTALLED_VERSION:
92 | check_new_version = True
93 | else:
94 | check_new_version = LooseVersion(current_version) < LooseVersion(last_release_version)
95 | except AttributeError:
96 | check_new_version = False
97 |
98 | if check_new_version:
99 | logging.info(f"Upgrading {self.PROJECT} : from {current_version} to {last_release_version}...")
100 | return github_instance.download_binary(replace=True)
101 | else:
102 | logging.info(f"Nothing to do : {self.PROJECT} is up to date")
103 | if launcher_instance:
104 | inc_progress(BINARY_DOWNLOAD_PROGRESS_SIZE)
105 | return binary_path
106 |
107 |
108 | class OctoBotVersion(Version):
109 | PROJECT = OCTOBOT_NAME
110 |
111 |
112 | class LauncherVersion(Version):
113 | PROJECT = PROJECT_NAME
114 |
--------------------------------------------------------------------------------
/launcher/tools/github.py:
--------------------------------------------------------------------------------
1 | # Drakkar-Software OctoBot-Launcher
2 | # Copyright (c) Drakkar-Software, All rights reserved.
3 | #
4 | # This library is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library.
16 | import json
17 | import logging
18 | import os
19 |
20 | import requests
21 |
22 | from launcher import GITHUB_API_CONTENT_URL, LAUNCHER_GITHUB_REPOSITORY, \
23 | OCTOBOT_BINARY_GITHUB_REPOSITORY, WINDOWS_OS_NAME, DeliveryPlatformsName, LINUX_OS_NAME, OCTOBOT_NAME, PROJECT_NAME, \
24 | inc_progress, OCTOBOT_DEV_PHASE
25 | from launcher.tools import BINARY_DOWNLOAD_PROGRESS_SIZE
26 |
27 |
28 | class Github:
29 | PROJECT_REPOSITORY = ""
30 | PROJECT = ""
31 |
32 | def get_latest_release_url(self):
33 | return f"{GITHUB_API_CONTENT_URL}/repos/{self.PROJECT_REPOSITORY}/releases/latest"
34 |
35 | def get_latest_release_data(self):
36 | return json.loads(requests.get(self.get_latest_release_url()).text)
37 |
38 | def get_asset_from_release_data(self):
39 | latest_release_data = self.get_latest_release_data()
40 | os_name = None
41 |
42 | # windows
43 | if os.name == WINDOWS_OS_NAME:
44 | os_name = DeliveryPlatformsName.WINDOWS
45 |
46 | # linux
47 | if os.name == LINUX_OS_NAME:
48 | os_name = DeliveryPlatformsName.LINUX
49 |
50 | # mac
51 | if os.name == 'mac':
52 | os_name = DeliveryPlatformsName.MAC
53 |
54 | # search for corresponding release
55 | try:
56 | for asset in latest_release_data["assets"]:
57 | asset_name, _ = os.path.splitext(asset["name"])
58 | if f"{self.PROJECT}_{os_name.value}" in asset_name:
59 | return asset
60 | except KeyError as e:
61 | logging.error(f"Can't find any asset : {e}")
62 | return None
63 |
64 | def get_current_server_version(self, latest_release_data=None):
65 | if not latest_release_data:
66 | latest_release_data = self.get_latest_release_data()
67 | try:
68 | tag_name = latest_release_data["tag_name"]
69 | dev_phase_tag = f"-{OCTOBOT_DEV_PHASE}"
70 | if dev_phase_tag in tag_name:
71 | return tag_name.split(dev_phase_tag)[0]
72 | else:
73 | return tag_name
74 | except KeyError:
75 | return None
76 |
77 | def download_binary(self, replace=False):
78 | binary = self.get_asset_from_release_data()
79 |
80 | if binary:
81 | final_size = binary["size"]
82 | increment = (BINARY_DOWNLOAD_PROGRESS_SIZE / (final_size / 1024))
83 |
84 | r = requests.get(binary["browser_download_url"], stream=True)
85 |
86 | binary_name, binary_ext = os.path.splitext(binary["name"])
87 | path = f"{self.PROJECT}{binary_ext}"
88 |
89 | if r.status_code == 200:
90 |
91 | if replace and os.path.isfile(path):
92 | try:
93 | os.remove(path)
94 | except OSError as e:
95 | logging.error(f"Can't remove old version binary : {e}")
96 |
97 | with open(path, 'wb') as f:
98 | for chunk in r.iter_content(1024):
99 | f.write(chunk)
100 | inc_progress(increment)
101 |
102 | return path
103 | else:
104 | logging.error("Release not found on server")
105 | return None
106 |
107 | def update_binary(self, version_instance, force_package=False, force_binary=False):
108 | # parse latest release
109 | try:
110 | logging.info(f"{self.PROJECT} is checking for updates...")
111 | latest_release_data = self.get_latest_release_data()
112 |
113 | # try to find binary / package
114 | binary_path = version_instance.get_local_binary()
115 |
116 | if (version_instance.is_package_installed() and not force_binary) or force_package:
117 | version_instance.download_package()
118 | return binary_path
119 | else:
120 | # try to find in current folder binary
121 | # if current octobot binary found
122 | if binary_path:
123 | logging.info(f"{self.PROJECT} installation found, analyzing...")
124 | return version_instance.get_local_version_or_download(self, binary_path, latest_release_data)
125 | else:
126 | return self.download_binary(latest_release_data)
127 | except Exception as e:
128 | logging.exception(f"Failed to download latest release data : {e}")
129 |
130 |
131 | class GithubOctoBot(Github):
132 | PROJECT_REPOSITORY = OCTOBOT_BINARY_GITHUB_REPOSITORY
133 | PROJECT = OCTOBOT_NAME
134 |
135 |
136 | class GithubLauncher(Github):
137 | PROJECT_REPOSITORY = LAUNCHER_GITHUB_REPOSITORY
138 | PROJECT = PROJECT_NAME
139 |
--------------------------------------------------------------------------------
/launcher/tools/environment.py:
--------------------------------------------------------------------------------
1 | # Drakkar-Software OctoBot-Launcher
2 | # Copyright (c) Drakkar-Software, All rights reserved.
3 | #
4 | # This library is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library.
16 | import logging
17 | import os
18 | import subprocess
19 |
20 | import urllib.request
21 |
22 | from launcher import CONFIG_FILE, OCTOBOT_GITHUB_REPOSITORY, \
23 | GITHUB_RAW_CONTENT_URL, OCTOBOT_REFERENCE_BRANCH, DEFAULT_CONFIG_FILE, LOGGING_CONFIG_FILE, \
24 | CONFIG_DEFAULT_EVALUATOR_FILE, CONFIG_DEFAULT_TRADING_FILE, OCTOBOT_NAME, LINUX_OS_NAME, MAC_OS_NAME, \
25 | TENTACLES_PATH, inc_progress, FORCE_BINARY, CONFIG_FILE_SCHEMA_WITH_PATH, STRATEGY_OPTIMIZER_DATA_FOLDER
26 | from launcher.tools import executor
27 | from launcher.tools.github import GithubOctoBot
28 | from launcher.tools.version import OctoBotVersion
29 |
30 | FOLDERS_TO_CREATE = ["logs", "backtesting/collector/data", STRATEGY_OPTIMIZER_DATA_FOLDER]
31 | STRATEGY_OPTIMIZER_DATA = [
32 | "binance_ADA_BTC_20180722_223335.data",
33 | "binance_BTC_USDT_20180428_121156.data",
34 | "binance_ETH_USDT_20180716_131148.data",
35 | "binance_ICX_BTC_20180716_131148.data",
36 | "binance_NEO_BTC_20180716_131148.data",
37 | "binance_ONT_BTC_20180722_230900.data",
38 | "binance_POWR_BTC_20180722_234855.data",
39 | "binance_VEN_BTC_20180716_131148.data",
40 | "binance_XLM_BTC_20180722_234305.data",
41 | "binance_XRB_BTC_20180716_131148.data",
42 | "bittrex_ADA_BTC_20180722_223357.data",
43 | "bittrex_ETC_BTC_20180726_210341.data",
44 | "bittrex_NEO_BTC_20180722_195942.data",
45 | "bittrex_WAX_BTC_20180726_205032.data",
46 | "bittrex_XRP_BTC_20180726_210927.data",
47 | "bittrex_XVG_BTC_20180726_211225.data"
48 | ]
49 |
50 | INSTALL_DOWNLOAD = [
51 | (
52 | f"{GITHUB_RAW_CONTENT_URL}/cjhutto/vaderSentiment/master/vaderSentiment/emoji_utf8_lexicon.txt",
53 | "vaderSentiment/emoji_utf8_lexicon.txt"),
54 | (
55 | f"{GITHUB_RAW_CONTENT_URL}/cjhutto/vaderSentiment/master/vaderSentiment/vader_lexicon.txt",
56 | "vaderSentiment/vader_lexicon.txt"),
57 | ]
58 |
59 | OCTOBOT_REPOSITORY_FILES_ROOT = f"{GITHUB_RAW_CONTENT_URL}/{OCTOBOT_GITHUB_REPOSITORY}/{OCTOBOT_REFERENCE_BRANCH}"
60 |
61 | INSTALL_DOWNLOAD += [(f"{OCTOBOT_REPOSITORY_FILES_ROOT}/{STRATEGY_OPTIMIZER_DATA_FOLDER}/{data_file}",
62 | f"{STRATEGY_OPTIMIZER_DATA_FOLDER}/{data_file}")
63 | for data_file in STRATEGY_OPTIMIZER_DATA]
64 |
65 | FILES_TO_DOWNLOAD = [
66 | (
67 | f"{OCTOBOT_REPOSITORY_FILES_ROOT}/{DEFAULT_CONFIG_FILE}", CONFIG_FILE
68 | ),
69 | (
70 | f"{OCTOBOT_REPOSITORY_FILES_ROOT}/{DEFAULT_CONFIG_FILE}", DEFAULT_CONFIG_FILE
71 | ),
72 | (
73 | f"{OCTOBOT_REPOSITORY_FILES_ROOT}/{CONFIG_FILE_SCHEMA_WITH_PATH}", CONFIG_FILE_SCHEMA_WITH_PATH
74 | ),
75 | (
76 | f"{OCTOBOT_REPOSITORY_FILES_ROOT}/{CONFIG_DEFAULT_EVALUATOR_FILE}", CONFIG_DEFAULT_EVALUATOR_FILE
77 | ),
78 | (
79 | f"{OCTOBOT_REPOSITORY_FILES_ROOT}/{CONFIG_DEFAULT_TRADING_FILE}", CONFIG_DEFAULT_TRADING_FILE
80 | ),
81 | (
82 | f"{OCTOBOT_REPOSITORY_FILES_ROOT}/{LOGGING_CONFIG_FILE}", LOGGING_CONFIG_FILE
83 | )
84 | ]
85 |
86 | LIB_FILES_DOWNLOAD_PROGRESS_SIZE = 5
87 | CREATE_FOLDERS_PROGRESS_SIZE = 5
88 |
89 |
90 | def create_environment():
91 | inc_progress(0, to_min=True)
92 | logging.info(f"{OCTOBOT_NAME} is checking your environment...")
93 |
94 | inc_progress(1)
95 | ensure_file_environment(INSTALL_DOWNLOAD)
96 |
97 | inc_progress(LIB_FILES_DOWNLOAD_PROGRESS_SIZE - 1)
98 | inc_progress(CREATE_FOLDERS_PROGRESS_SIZE)
99 |
100 | logging.info(f"Your {OCTOBOT_NAME} environment is ready !")
101 |
102 |
103 | def install_bot(force_package=False):
104 | create_environment()
105 |
106 | binary_path = GithubOctoBot().update_binary(OctoBotVersion(), force_package=force_package,
107 | force_binary=FORCE_BINARY)
108 |
109 | # give binary execution rights if necessary
110 | if binary_path:
111 | binary_execution_rights(binary_path)
112 |
113 | # if update tentacles
114 | if binary_path:
115 | executable_path = OctoBotVersion().get_local_binary(force_binary=FORCE_BINARY)
116 | update_tentacles(executable_path, force_install=True)
117 | else:
118 | logging.error(f"No {OCTOBOT_NAME} found to update tentacles.")
119 |
120 |
121 | def _ensure_directory(file_path):
122 | directory = os.path.dirname(file_path)
123 |
124 | if not os.path.exists(directory) and directory:
125 | os.makedirs(directory)
126 |
127 |
128 | def ensure_minimum_environment():
129 | try:
130 | for folder in FOLDERS_TO_CREATE:
131 | if not os.path.exists(folder) and folder:
132 | os.makedirs(folder)
133 |
134 | ensure_file_environment(FILES_TO_DOWNLOAD)
135 | except Exception as e:
136 | print(f"Error when creating minimum launcher environment: {e} this should not prevent launcher "
137 | f"from working.")
138 |
139 |
140 | def ensure_file_environment(file_to_download):
141 | # download files
142 | for file_to_dl in file_to_download:
143 |
144 | _ensure_directory(file_to_dl[1])
145 |
146 | file_name = file_to_dl[1]
147 | if not os.path.isfile(file_name) and file_name:
148 | urllib.request.urlretrieve(file_to_dl[0], file_name)
149 |
150 |
151 | def update_tentacles(binary_path, force_install=False):
152 | if binary_path:
153 | # update tentacles if installed
154 | if not force_install and os.path.exists(TENTACLES_PATH):
155 | executor.execute_command_on_current_binary(binary_path, ["-p", "update", "all"])
156 | logging.info(f"Tentacles : all default tentacles have been updated.")
157 | else:
158 | executor.execute_command_on_current_binary(binary_path, ["-p", "install", "all", "force"])
159 | logging.info(f"Tentacles : all default tentacles have been installed.")
160 |
161 |
162 | def binary_execution_rights(binary_path):
163 | if os.name in [LINUX_OS_NAME, MAC_OS_NAME]:
164 | try:
165 | rights_process = subprocess.Popen(["chmod", "+x", binary_path])
166 | except Exception as e:
167 | logging.error(f"Failed to give execution rights to {binary_path} : {e}")
168 | rights_process = None
169 |
170 | if not rights_process:
171 | # show message if user has to type the command
172 | message = f"{OCTOBOT_NAME} binary need execution rights, " \
173 | f"please type in a command line 'sudo chmod +x ./{OCTOBOT_NAME}'"
174 | logging.warning(message)
175 | # if self.launcher_app:
176 | # self.launcher_app.show_alert(f"{message} and then press OK", bitmap=WARNING)
177 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------