first commit
This commit is contained in:
11
.eslintrc.js
Executable file
11
.eslintrc.js
Executable file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@nextcloud'
|
||||
],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-undef':'off',
|
||||
'eqeqeq': 'off',
|
||||
'no-unused-expressions':'off',
|
||||
},
|
||||
};
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/node_modules
|
||||
.sass-cache
|
||||
vendor
|
||||
build.sh
|
||||
test.php
|
||||
js/app*
|
||||
exclude
|
||||
64
.travis.yml
Normal file
64
.travis.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
sudo: false
|
||||
dist: trusty
|
||||
language: php
|
||||
php:
|
||||
- 5.6
|
||||
- 7
|
||||
- 7.1
|
||||
env:
|
||||
global:
|
||||
- CORE_BRANCH=stable15
|
||||
matrix:
|
||||
- DB=pgsql
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: DB=pgsql CORE_BRANCH=master
|
||||
include:
|
||||
- php: 5.6
|
||||
env: DB=sqlite
|
||||
- php: 5.6
|
||||
env: DB=mysql
|
||||
- php: 5.6
|
||||
env: DB=pgsql CORE_BRANCH=master
|
||||
fast_finish: true
|
||||
|
||||
|
||||
before_install:
|
||||
# enable a display for running JavaScript tests
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- nvm install 8
|
||||
- npm install -g npm@latest
|
||||
- make
|
||||
- make appstore
|
||||
# install core
|
||||
- cd ../
|
||||
- git clone https://github.com/nextcloud/server.git --recursive --depth 1 -b $CORE_BRANCH nextcloud
|
||||
- mv "$TRAVIS_BUILD_DIR" nextcloud/apps/ncdownloader
|
||||
|
||||
before_script:
|
||||
- if [[ "$DB" == 'pgsql' ]]; then createuser -U travis -s oc_autotest; fi
|
||||
- if [[ "$DB" == 'mysql' ]]; then mysql -u root -e 'create database oc_autotest;'; fi
|
||||
- if [[ "$DB" == 'mysql' ]]; then mysql -u root -e "CREATE USER 'oc_autotest'@'localhost' IDENTIFIED BY '';"; fi
|
||||
- if [[ "$DB" == 'mysql' ]]; then mysql -u root -e "grant all on oc_autotest.* to 'oc_autotest'@'localhost';"; fi
|
||||
- cd nextcloud
|
||||
- mkdir data
|
||||
- ./occ maintenance:install --database-name oc_autotest --database-user oc_autotest --admin-user admin --admin-pass admin --database $DB --database-pass=''
|
||||
- ./occ app:enable ncdownloader
|
||||
- php -S localhost:8080 &
|
||||
- cd apps/ncdownloader
|
||||
|
||||
script:
|
||||
- make test
|
||||
|
||||
after_failure:
|
||||
- cat ../../data/nextcloud.log
|
||||
|
||||
addons:
|
||||
firefox: 'latest'
|
||||
mariadb: '10.1'
|
||||
|
||||
services:
|
||||
- postgresql
|
||||
- mariadb
|
||||
661
COPYING
Normal file
661
COPYING
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
155
Makefile
Normal file
155
Makefile
Normal file
@@ -0,0 +1,155 @@
|
||||
# This file is licensed under the Affero General Public License version 3 or
|
||||
# later. See the COPYING file.
|
||||
# @author Bernhard Posselt <dev@bernhard-posselt.com>
|
||||
# @copyright Bernhard Posselt 2016
|
||||
|
||||
# Generic Makefile for building and packaging a Nextcloud app which uses npm and
|
||||
# Composer.
|
||||
#
|
||||
# Dependencies:
|
||||
# * make
|
||||
# * which
|
||||
# * curl: used if phpunit and composer are not installed to fetch them from the web
|
||||
# * tar: for building the archive
|
||||
# * npm: for building and testing everything JS
|
||||
#
|
||||
# If no composer.json is in the app root directory, the Composer step
|
||||
# will be skipped. The same goes for the package.json which can be located in
|
||||
# the app root or the js/ directory.
|
||||
#
|
||||
# The npm command by launches the npm build script:
|
||||
#
|
||||
# npm run build
|
||||
#
|
||||
# The npm test command launches the npm test script:
|
||||
#
|
||||
# npm run test
|
||||
#
|
||||
# The idea behind this is to be completely testing and build tool agnostic. All
|
||||
# build tools and additional package managers should be installed locally in
|
||||
# your project, since this won't pollute people's global namespace.
|
||||
#
|
||||
# The following npm scripts in your package.json install and update the bower
|
||||
# and npm dependencies and use gulp as build system (notice how everything is
|
||||
# run from the node_modules folder):
|
||||
#
|
||||
# "scripts": {
|
||||
# "test": "node node_modules/gulp-cli/bin/gulp.js karma",
|
||||
# "prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
|
||||
# "build": "node node_modules/gulp-cli/bin/gulp.js"
|
||||
# },
|
||||
|
||||
app_name=$(notdir $(CURDIR))
|
||||
build_tools_directory=$(CURDIR)/build/tools
|
||||
source_build_directory=$(CURDIR)/build/artifacts/source
|
||||
source_package_name=$(source_build_directory)/$(app_name)
|
||||
appstore_build_directory=$(CURDIR)/build/artifacts/appstore
|
||||
appstore_package_name=$(appstore_build_directory)/$(app_name)
|
||||
npm=$(shell which npm 2> /dev/null)
|
||||
composer=$(shell which composer 2> /dev/null)
|
||||
|
||||
all: build
|
||||
|
||||
# Fetches the PHP and JS dependencies and compiles the JS. If no composer.json
|
||||
# is present, the composer step is skipped, if no package.json or js/package.json
|
||||
# is present, the npm step is skipped
|
||||
.PHONY: build
|
||||
build:
|
||||
ifneq (,$(wildcard $(CURDIR)/composer.json))
|
||||
make composer
|
||||
endif
|
||||
ifneq (,$(wildcard $(CURDIR)/package.json))
|
||||
make npm
|
||||
endif
|
||||
ifneq (,$(wildcard $(CURDIR)/js/package.json))
|
||||
make npm
|
||||
endif
|
||||
|
||||
# Installs and updates the composer dependencies. If composer is not installed
|
||||
# a copy is fetched from the web
|
||||
.PHONY: composer
|
||||
composer:
|
||||
ifeq (, $(composer))
|
||||
@echo "No composer command available, downloading a copy from the web"
|
||||
mkdir -p $(build_tools_directory)
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
mv composer.phar $(build_tools_directory)
|
||||
php $(build_tools_directory)/composer.phar install --prefer-dist
|
||||
else
|
||||
composer install --prefer-dist
|
||||
endif
|
||||
|
||||
# Installs npm dependencies
|
||||
.PHONY: npm
|
||||
npm:
|
||||
ifeq (,$(wildcard $(CURDIR)/package.json))
|
||||
cd js && $(npm) run build
|
||||
else
|
||||
npm run build
|
||||
endif
|
||||
|
||||
# Removes the appstore build
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ./build
|
||||
|
||||
# Same as clean but also removes dependencies installed by composer, bower and
|
||||
# npm
|
||||
.PHONY: distclean
|
||||
distclean: clean
|
||||
rm -rf vendor
|
||||
rm -rf node_modules
|
||||
rm -rf js/vendor
|
||||
rm -rf js/node_modules
|
||||
|
||||
# Builds the source and appstore package
|
||||
.PHONY: dist
|
||||
dist:
|
||||
make source
|
||||
make appstore
|
||||
|
||||
# Builds the source package
|
||||
.PHONY: source
|
||||
source:
|
||||
rm -rf $(source_build_directory)
|
||||
mkdir -p $(source_build_directory)
|
||||
tar cvzf $(source_package_name).tar.gz ../$(app_name) \
|
||||
--exclude-vcs \
|
||||
--exclude="../$(app_name)/build" \
|
||||
--exclude="../$(app_name)/js/node_modules" \
|
||||
--exclude="../$(app_name)/node_modules" \
|
||||
--exclude="../$(app_name)/*.log" \
|
||||
--exclude="../$(app_name)/js/*.log" \
|
||||
|
||||
# Builds the source package for the app store, ignores php and js tests
|
||||
.PHONY: appstore
|
||||
appstore:
|
||||
rm -rf $(appstore_build_directory)
|
||||
mkdir -p $(appstore_build_directory)
|
||||
tar cvzf $(appstore_package_name).tar.gz ../$(app_name) \
|
||||
--exclude-vcs \
|
||||
--exclude="../$(app_name)/build" \
|
||||
--exclude="../$(app_name)/tests" \
|
||||
--exclude="../$(app_name)/Makefile" \
|
||||
--exclude="../$(app_name)/*.log" \
|
||||
--exclude="../$(app_name)/phpunit*xml" \
|
||||
--exclude="../$(app_name)/composer.*" \
|
||||
--exclude="../$(app_name)/js/node_modules" \
|
||||
--exclude="../$(app_name)/js/tests" \
|
||||
--exclude="../$(app_name)/js/test" \
|
||||
--exclude="../$(app_name)/js/*.log" \
|
||||
--exclude="../$(app_name)/js/package.json" \
|
||||
--exclude="../$(app_name)/js/bower.json" \
|
||||
--exclude="../$(app_name)/js/karma.*" \
|
||||
--exclude="../$(app_name)/js/protractor.*" \
|
||||
--exclude="../$(app_name)/package.json" \
|
||||
--exclude="../$(app_name)/bower.json" \
|
||||
--exclude="../$(app_name)/karma.*" \
|
||||
--exclude="../$(app_name)/protractor\.*" \
|
||||
--exclude="../$(app_name)/.*" \
|
||||
--exclude="../$(app_name)/js/.*" \
|
||||
|
||||
.PHONY: test
|
||||
test: composer
|
||||
$(CURDIR)/vendor/phpunit/phpunit/phpunit -c phpunit.xml
|
||||
$(CURDIR)/vendor/phpunit/phpunit/phpunit -c phpunit.integration.xml
|
||||
8
README.md
Normal file
8
README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
An easy-to-use web interface for downloading bittorrents, videos from twitter, youtube and the likes
|
||||
|
||||
- built-in torrent search
|
||||
- Start and stop Aria2 process, manage Aria2 from the web;
|
||||
- Download videos from youtube, twitter and other sites;
|
||||
<img width="1057" alt="nc1" src="https://user-images.githubusercontent.com/3911975/132008248-dbb6036e-e3c2-4e5c-9274-d2b1ae091373.png">
|
||||
<img width="890" alt="nc2" src="https://user-images.githubusercontent.com/3911975/132008308-dec2a7ba-4387-441e-9ded-538d61fbccf0.png">
|
||||
<img width="287" alt="nc3" src="https://user-images.githubusercontent.com/3911975/132008501-980fafd4-90a0-4a1c-937e-ecf5144202fd.png">
|
||||
23
appinfo/app.php
Normal file
23
appinfo/app.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud - ncdownloader
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the LICENSE file.
|
||||
*
|
||||
* @author Xavier Beurois <www.sgc-univ.net>
|
||||
* @copyright Xavier Beurois 2015
|
||||
*/
|
||||
|
||||
namespace OCA\NcDownloader\AppInfo;
|
||||
|
||||
\OC::$server->getNavigationManager()->add([
|
||||
'id' => 'ncdownloader',
|
||||
'order' => 10,
|
||||
'href' => \OC::$server->getURLGenerator()->linkToRoute('ncdownloader.Main.Index'),
|
||||
'icon' => \OC::$server->getURLGenerator()->imagePath('ncdownloader', 'ncdownloader.svg'),
|
||||
'name' => 'ncdownloader'
|
||||
]);
|
||||
|
||||
//\OCP\App::registerAdmin('ncdownloader', 'settings/admin');
|
||||
//\OCP\App::registerPersonal('ncdownloader', 'settings/personal');
|
||||
80
appinfo/application.php
Normal file
80
appinfo/application.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\AppInfo;
|
||||
|
||||
use OCA\NcDownloader\Controller\MainController;
|
||||
use OCA\NcDownloader\Controller\Aria2Controller;
|
||||
use OCA\NcDownloader\Tools\Aria2;
|
||||
use OCA\NcDownloader\Tools\Settings;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\IContainer;
|
||||
use \OC\Files\Filesystem;
|
||||
|
||||
class Application extends App
|
||||
{
|
||||
public function __construct(array $urlParams = array())
|
||||
{
|
||||
parent::__construct('ncdownloader', $urlParams);
|
||||
$container = $this->getContainer();
|
||||
$container->registerService('UserId', function (IContainer $container) {
|
||||
$user = \OC::$server->getUserSession()->getUser();
|
||||
return ($user) ? $user->getUID() : '';
|
||||
});
|
||||
|
||||
$container->registerService('Aria2', function (IContainer $container) {
|
||||
$uid = $container->query('UserId');
|
||||
return new Aria2($this->getConfig($uid));
|
||||
});
|
||||
|
||||
$container->registerService('Settings', function (IContainer $container) {
|
||||
$uid = $container->query('UserId');
|
||||
return new Settings($uid);
|
||||
});
|
||||
|
||||
$container->registerService('MainController', function (IContainer $container) {
|
||||
return new MainController(
|
||||
$container->query('AppName'),
|
||||
$container->query('Request'),
|
||||
$container->query('UserId'),
|
||||
\OC::$server->getL10N('ncdownloader'),
|
||||
\OC::$server->getRootFolder(),
|
||||
$container->query('Aria2')
|
||||
);
|
||||
});
|
||||
|
||||
$container->registerService('Aria2Controller', function (IContainer $container) {
|
||||
return new Aria2Controller(
|
||||
$container->query('AppName'),
|
||||
$container->query('Request'),
|
||||
$container->query('UserId'),
|
||||
\OC::$server->getL10N('ncdownloader'),
|
||||
\OC::$server->getRootFolder(),
|
||||
$container->query('Aria2')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private function getConfig($uid)
|
||||
{
|
||||
//$this->config = \OC::$server->getAppConfig();
|
||||
$this->settings = new Settings($uid);
|
||||
$this->userFolder = Filesystem::getRoot();
|
||||
$this->dataDir = \OC::$server->getSystemConfig()->getValue('datadirectory');
|
||||
//relative nextcloud user path
|
||||
$this->downloadDir = $this->settings->get('ncd_downloader_dir') ?? "/Downloads";
|
||||
$this->torrentsDir = $this->settings->get('torrents_dir');
|
||||
//get the absolute path
|
||||
$this->realDownloadDir = $this->dataDir . $this->userFolder . $this->downloadDir;
|
||||
$aria2_dir = $this->dataDir . "/aria2";
|
||||
$this->appPath = \OC::$server->getAppManager()->getAppPath('ncdownloader');
|
||||
$settings['seed_time'] = $this->settings->get("ncd_seed_time");
|
||||
$settings['seed_ratio'] = $this->settings->get("ncd_seed_ratio");
|
||||
if (is_array($customSettings = $this->settings->getAria2())) {
|
||||
$settings = array_merge($customSettings, $settings);
|
||||
}
|
||||
$token = $this->settings->setType(1)->get('ncd_rpctoken');
|
||||
$config = ['dir' => $this->realDownloadDir, 'conf_dir' => $aria2_dir, 'token' => $token, 'settings' => $settings];
|
||||
return $config;
|
||||
}
|
||||
|
||||
}
|
||||
32
appinfo/info.xml
Normal file
32
appinfo/info.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0"?>
|
||||
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||
<id>ncdownloader</id>
|
||||
<name>Nc Downloader</name>
|
||||
<summary>Aria2 and youtube-dl web gui for nextcloud</summary>
|
||||
<description><![CDATA[Nexcloud Frontend for Aria2 and Youtube-dl]]></description>
|
||||
<version>0.0.1</version>
|
||||
<licence>agpl</licence>
|
||||
<author mail="freefallbenson@gmail.com" homepage="https://github.com/shiningw">jiaxinhuang</author>
|
||||
<namespace>NcDownloader</namespace>
|
||||
<category>tools</category>
|
||||
<bugs>https://github.com/shiningw</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="18" max-version="22"/>
|
||||
</dependencies>
|
||||
<navigations>
|
||||
<navigation>
|
||||
<name>ncdownloader</name>
|
||||
<route>ncdownloader.Main.Index</route>
|
||||
</navigation>
|
||||
</navigations>
|
||||
<settings>
|
||||
<admin>OCA\NcDownloader\Settings\Admin</admin>
|
||||
<personal>OCA\NcDownloader\Settings\Personal</personal>
|
||||
<admin-section>OCA\NcDownloader\Settings\AdminSection</admin-section>
|
||||
<personal-section>OCA\NcDownloader\Settings\PersonalSection</personal-section>
|
||||
</settings>
|
||||
<commands>
|
||||
<command>OCA\NcDownloader\Command\Aria2Command</command>
|
||||
</commands>
|
||||
|
||||
</info>
|
||||
28
appinfo/routes.php
Normal file
28
appinfo/routes.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Create your routes in here. The name is the lowercase name of the controller
|
||||
* without the controller part, the stuff after the hash is the method.
|
||||
* e.g. page#index -> OCA\NcDownloader\Controller\Aria2Controller->index()
|
||||
*
|
||||
* The controller class has to be registered in the application.php file since
|
||||
* it's instantiated in there
|
||||
*/
|
||||
return [
|
||||
'routes' => [
|
||||
['name' => 'Main#Index', 'url' => '/', 'verb' => 'GET'],
|
||||
['name' => 'main#newDownload', 'url' => '/new', 'verb' => 'POST'],
|
||||
['name' => 'Aria2#Action', 'url' => '/aria2/{path}', 'verb' => 'POST'],
|
||||
['name' => 'Aria2#getStatus', 'url' => '/status/{path}', 'verb' => 'POST'],
|
||||
['name' => 'Aria2#Update', 'url' => '/update', 'verb' => 'GET'],
|
||||
//['name' => 'main#checkStatus', 'url' => '/checkstatus', 'verb' => 'POST'],
|
||||
// AdminSettings
|
||||
['name' => 'Settings#Admin', 'url' => '/admin/save', 'verb' => 'POST'],
|
||||
// PersonalSettings
|
||||
['name' => 'Settings#Personal', 'url' => '/personal/save', 'verb' => 'POST'],
|
||||
['name' => 'Settings#aria2Get', 'url' => '/personal/aria2/get', 'verb' => 'POST'],
|
||||
['name' => 'Settings#aria2Save', 'url' => '/personal/aria2/save', 'verb' => 'POST'],
|
||||
['name' => 'Settings#aria2Delete', 'url' => '/personal/aria2/delete', 'verb' => 'POST'],
|
||||
|
||||
]
|
||||
];
|
||||
|
||||
3
babel.config.js
Executable file
3
babel.config.js
Executable file
@@ -0,0 +1,3 @@
|
||||
const babelConfig = require('@nextcloud/babel-config')
|
||||
|
||||
module.exports = babelConfig
|
||||
7
composer.json
Normal file
7
composer.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"require": {
|
||||
"symfony/dom-crawler": "^5.3",
|
||||
"symfony/http-client": "^5.3",
|
||||
"symfony/css-selector": "^5.3"
|
||||
}
|
||||
}
|
||||
962
composer.lock
generated
Normal file
962
composer.lock
generated
Normal file
@@ -0,0 +1,962 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "eb403135f5d32c6090e576123d21b29f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/container.git",
|
||||
"reference": "8622567409010282b7aeebe4bb841fe98b58dcaf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf",
|
||||
"reference": "8622567409010282b7aeebe4bb841fe98b58dcaf",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Container\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common Container Interface (PHP FIG PSR-11)",
|
||||
"homepage": "https://github.com/php-fig/container",
|
||||
"keywords": [
|
||||
"PSR-11",
|
||||
"container",
|
||||
"container-interface",
|
||||
"container-interop",
|
||||
"psr"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/container/issues",
|
||||
"source": "https://github.com/php-fig/container/tree/1.1.1"
|
||||
},
|
||||
"time": "2021-03-05T17:36:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "Psr/Log/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for logging libraries",
|
||||
"homepage": "https://github.com/php-fig/log",
|
||||
"keywords": [
|
||||
"log",
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/1.1.4"
|
||||
},
|
||||
"time": "2021-05-03T11:20:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v5.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "7fb120adc7f600a59027775b224c13a33530dd90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90",
|
||||
"reference": "7fb120adc7f600a59027775b224c13a33530dd90",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\CssSelector\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Jean-François Simon",
|
||||
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Converts CSS selectors to XPath expressions",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/css-selector/tree/v5.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-21T12:38:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
|
||||
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.4-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"function.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-23T23:28:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dom-crawler",
|
||||
"version": "v5.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dom-crawler.git",
|
||||
"reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
|
||||
"reference": "c7eef3a60ccfdd8eafe07f81652e769ac9c7146c",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/deprecation-contracts": "^2.1",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"conflict": {
|
||||
"masterminds/html5": "<2.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"masterminds/html5": "^2.6",
|
||||
"symfony/css-selector": "^4.4|^5.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/css-selector": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\DomCrawler\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Eases DOM navigation for HTML and XML documents",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dom-crawler/tree/v5.3.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-08-29T19:32:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v5.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "da8638ffecefc4e8ba2bc848d7b61a408119b333"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/da8638ffecefc4e8ba2bc848d7b61a408119b333",
|
||||
"reference": "da8638ffecefc4e8ba2bc848d7b61a408119b333",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/deprecation-contracts": "^2.1",
|
||||
"symfony/http-client-contracts": "^2.4",
|
||||
"symfony/polyfill-php73": "^1.11",
|
||||
"symfony/polyfill-php80": "^1.16",
|
||||
"symfony/service-contracts": "^1.0|^2"
|
||||
},
|
||||
"provide": {
|
||||
"php-http/async-client-implementation": "*",
|
||||
"php-http/client-implementation": "*",
|
||||
"psr/http-client-implementation": "1.0",
|
||||
"symfony/http-client-implementation": "2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"amphp/amp": "^2.5",
|
||||
"amphp/http-client": "^4.2.1",
|
||||
"amphp/http-tunnel": "^1.0",
|
||||
"amphp/socket": "^1.1",
|
||||
"guzzlehttp/promises": "^1.4",
|
||||
"nyholm/psr7": "^1.0",
|
||||
"php-http/httplug": "^1.0|^2.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/dependency-injection": "^4.4|^5.0",
|
||||
"symfony/http-kernel": "^4.4.13|^5.1.5",
|
||||
"symfony/process": "^4.4|^5.0",
|
||||
"symfony/stopwatch": "^4.4|^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\HttpClient\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v5.3.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-08-28T16:24:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client-contracts.git",
|
||||
"reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
|
||||
"reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/http-client-implementation": ""
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.4-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Contracts\\HttpClient\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Generic abstractions related to HTTP clients",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"abstractions",
|
||||
"contracts",
|
||||
"decoupling",
|
||||
"interfaces",
|
||||
"interoperability",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-04-11T23:07:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-19T12:13:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.23.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6",
|
||||
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-05-27T12:26:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php73",
|
||||
"version": "v1.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php73.git",
|
||||
"reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010",
|
||||
"reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php73\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-19T12:13:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.23.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be",
|
||||
"reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-28T13:41:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/service-contracts.git",
|
||||
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
|
||||
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"psr/container": "^1.1"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/service-implementation": ""
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.4-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Contracts\\Service\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Generic abstractions related to writing services",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"abstractions",
|
||||
"contracts",
|
||||
"decoupling",
|
||||
"interfaces",
|
||||
"interoperability",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v2.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-04-01T10:43:52+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
35
css/autocomplete.css
Normal file
35
css/autocomplete.css
Normal file
@@ -0,0 +1,35 @@
|
||||
.suggestion-container {
|
||||
text-align: left;
|
||||
cursor : default;
|
||||
border : 1px solid #ccc;
|
||||
border-top: 0;
|
||||
background: #fff;
|
||||
box-shadow: -1px 1px 3px rgba(0, 0, 0, .1);
|
||||
position : absolute;
|
||||
display : none;
|
||||
z-index : 9999;
|
||||
max-height: 254px;
|
||||
overflow : hidden;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
position : relative;
|
||||
padding : 0 .6em;
|
||||
line-height : 23px;
|
||||
white-space : nowrap;
|
||||
overflow : hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size : 1.02em;
|
||||
color : #333;
|
||||
}
|
||||
|
||||
.suggestion-item b {
|
||||
font-weight: normal;
|
||||
color : #1f8dd6;
|
||||
}
|
||||
|
||||
.suggestion-item.selected {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
262
css/style.css
Normal file
262
css/style.css
Normal file
@@ -0,0 +1,262 @@
|
||||
.action-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
color : #337ab7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
color : #23527c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
outline : 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
#ncdownloader-form-wrapper input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div .number {
|
||||
background : none repeat scroll 0 0 #5f5853;
|
||||
border-radius: 999px;
|
||||
color : #FFFFFF;
|
||||
display : inline-block;
|
||||
font-size : 15px;
|
||||
font-style : italic;
|
||||
height : 20px;
|
||||
line-height : 20px;
|
||||
margin-right : 10px;
|
||||
padding : 0 8px;
|
||||
}
|
||||
|
||||
#ncdownloader-form-wrapper input[type=text] {
|
||||
padding: 0px 5px;
|
||||
width : 100%;
|
||||
}
|
||||
|
||||
#ncdownloader-table-data .table-cell {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.icon-power {
|
||||
background-image: url('../img/power.svg');
|
||||
}
|
||||
|
||||
.icon-purge {
|
||||
background-image: url('../img/trash.svg');
|
||||
}
|
||||
.icon-unpause {
|
||||
background-image: url('../img/play.svg');
|
||||
}
|
||||
#ncdownloader-action-links-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#ncdownloader-action-links-container ul {
|
||||
background-color: #ededed;
|
||||
}
|
||||
|
||||
#ncdownloader-action-links-container .action-link-item a:hover {
|
||||
background-color: #c2afaf;
|
||||
}
|
||||
|
||||
#ncdownloader-message-banner.success,
|
||||
.message-banner.success {
|
||||
color : #3c763d;
|
||||
background-color: #dff0d8;
|
||||
border-color : #d6e9c6;
|
||||
width : fit-content;
|
||||
}
|
||||
|
||||
#ncdownloader-message-banner.error,
|
||||
.message-banner.error {
|
||||
color : #a94442;
|
||||
background-color: #f2dede;
|
||||
border-color : #ebccd1;
|
||||
width : fit-content;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.action-links {
|
||||
/*visibility: hidden;*/
|
||||
display : none;
|
||||
position : absolute;
|
||||
background-color: #f1f1f1;
|
||||
min-width : 60px;
|
||||
box-shadow : 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index : 9999;
|
||||
}
|
||||
|
||||
.action-link-item a {
|
||||
color : black;
|
||||
padding : 6px 6px;
|
||||
text-decoration: none;
|
||||
display : block;
|
||||
}
|
||||
|
||||
.app-ncdownloader,
|
||||
#ncdownloader-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ncdownloader-content-wrapper {
|
||||
width : 100%;
|
||||
padding-top: 0.3em;
|
||||
}
|
||||
|
||||
#ncdownloader-form-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
#ncdownloader-table-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-ncdownloader #app-content #app-content-wrapper {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#app-content-wrapper #ncdownloader-table-content thead>tr {
|
||||
height : 30px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
#app-content-wrapper #ncdownloader-table-content thead>tr>th {
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
padding : 2px 0 2px 5px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
text-align : left;
|
||||
}
|
||||
|
||||
#ncdownloader-table-content thead.table-heading th {
|
||||
border-left: 1px solid rgb(216, 202, 202);
|
||||
}
|
||||
|
||||
#app-content-wrapper #ncdownloader-table-content .table-cell {
|
||||
border-bottom: 1px solid #9d9595;
|
||||
border-left : 1px solid #eee;
|
||||
|
||||
}
|
||||
|
||||
.app-navigation-entry-bullet {
|
||||
background-color: rgb(80, 80, 173);
|
||||
}
|
||||
|
||||
.notinstalled {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 800px) {}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
#ncdownloader-form-wrapper {
|
||||
position : relative;
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* from bootstrap */
|
||||
.visually-hidden,
|
||||
.visually-hidden-focusable:not(:focus):not(:focus-within) {
|
||||
position : absolute !important;
|
||||
width : 1px !important;
|
||||
height : 1px !important;
|
||||
padding : 0 !important;
|
||||
margin : -1px !important;
|
||||
overflow : hidden !important;
|
||||
clip : rect(0, 0, 0, 0) !important;
|
||||
white-space: nowrap !important;
|
||||
border : 0 !important;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spinner-border {
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
/* rtl :ignore */
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner-border {
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
/* rtl :ignore */
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
display : inline-block;
|
||||
width : 2rem;
|
||||
height : 2rem;
|
||||
vertical-align : -0.125em;
|
||||
border : 0.25em solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-radius : 50%;
|
||||
-webkit-animation : 0.75s linear infinite spinner-border;
|
||||
animation : 0.75s linear infinite spinner-border;
|
||||
}
|
||||
|
||||
.spinner-border-sm {
|
||||
width : 1rem;
|
||||
height : 1rem;
|
||||
border-width: 0.2em;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spinner-grow {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity : 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinner-grow {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity : 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner-grow {
|
||||
display : inline-block;
|
||||
width : 2rem;
|
||||
height : 2rem;
|
||||
vertical-align : -0.125em;
|
||||
background-color : currentColor;
|
||||
border-radius : 50%;
|
||||
opacity : 0;
|
||||
-webkit-animation: 0.75s linear infinite spinner-grow;
|
||||
animation : 0.75s linear infinite spinner-grow;
|
||||
}
|
||||
|
||||
.spinner-grow-sm {
|
||||
width : 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
.spinner-border,
|
||||
.spinner-grow {
|
||||
-webkit-animation-duration: 1.5s;
|
||||
animation-duration : 1.5s;
|
||||
}
|
||||
}
|
||||
81
css/table.css
Normal file
81
css/table.css
Normal file
@@ -0,0 +1,81 @@
|
||||
#ncdownloader-table-wrapper {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
border-radius: 4px;
|
||||
/*border : 1px solid #DADADA;*/
|
||||
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #fafafa; }
|
||||
#ncdownloader-table-wrapper .table-row,
|
||||
#ncdownloader-table-wrapper .table-row-search {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #dadada; }
|
||||
#ncdownloader-table-wrapper section.table-body,
|
||||
#ncdownloader-table-wrapper section.table-heading {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap; }
|
||||
#ncdownloader-table-wrapper .table-row,
|
||||
#ncdownloader-table-wrapper .table-row-search {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #dadada; }
|
||||
#ncdownloader-table-wrapper .table-heading .table-row,
|
||||
#ncdownloader-table-wrapper .table-heading .table-row-search {
|
||||
background-color: #ececec;
|
||||
color: #3e3e3e;
|
||||
font-weight: bold; }
|
||||
#ncdownloader-table-wrapper .table-heading .table-cell:hover {
|
||||
cursor: pointer;
|
||||
background-color: #c4afaf;
|
||||
/* box-shadow : 0px 1px 4px rgba(0, 0, 0, .08); */ }
|
||||
#ncdownloader-table-wrapper .table-cell {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
padding: 8px 3px;
|
||||
justify-content: left;
|
||||
align-items: left;
|
||||
transition: all 0.15s ease-in-out; }
|
||||
#ncdownloader-table-wrapper .table-cell:first-child {
|
||||
flex: 1 1 30%; }
|
||||
#ncdownloader-table-wrapper .table-cell:last-child {
|
||||
flex: 0 1 7%; }
|
||||
#ncdownloader-table-wrapper #table-cell-status,
|
||||
#ncdownloader-table-wrapper .table-heading-status {
|
||||
flex: 0 1 15%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
#ncdownloader-table-wrapper .table-cell:hover {
|
||||
cursor: pointer;
|
||||
background-color: #F0F0F0;
|
||||
/* box-shadow : 0px 1px 4px rgba(0, 0, 0, .08); */ }
|
||||
#ncdownloader-table-wrapper .row-sub-container {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
flex: 1; }
|
||||
#ncdownloader-table-wrapper .row-sub-container .table-cell {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #dadada; }
|
||||
#ncdownloader-table-wrapper .table-row:last-child,
|
||||
#ncdownloader-table-wrapper .row-sub-container .table-cell:last-child {
|
||||
border-bottom: 0; }
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
#ncdownloader-table-wrapper #ncdownloader-form-wrapper {
|
||||
position: relative;
|
||||
margin-top: 2em; }
|
||||
#ncdownloader-table-wrapper .table-cell:first-child {
|
||||
flex: 1 0 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
#ncdownloader-table-wrapper .table-cell:last-child {
|
||||
flex: 1 1 7%; } }
|
||||
123
css/table.scss
Normal file
123
css/table.scss
Normal file
@@ -0,0 +1,123 @@
|
||||
#ncdownloader-table-wrapper {
|
||||
display : flex;
|
||||
flex-flow : column nowrap;
|
||||
background-color: white;
|
||||
width : 100%;
|
||||
margin : 0 auto;
|
||||
border-radius : 4px;
|
||||
/*border : 1px solid #DADADA;*/
|
||||
box-shadow : 0px 1px 4px rgba(0, 0, 0, .08);
|
||||
justify-content : center;
|
||||
align-items : center;
|
||||
background-color: #fafafa;
|
||||
|
||||
.table-row,
|
||||
.table-row-search {
|
||||
display : flex;
|
||||
flex-flow : row nowrap;
|
||||
width : 100%;
|
||||
border-bottom: 1px solid #dadada;
|
||||
}
|
||||
|
||||
section.table-body,
|
||||
section.table-heading {
|
||||
width : 100%;
|
||||
display : flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
|
||||
.table-row,
|
||||
.table-row-search {
|
||||
display : flex;
|
||||
flex-flow : row nowrap;
|
||||
width : 100%;
|
||||
border-bottom: 1px solid #dadada;
|
||||
}
|
||||
|
||||
.table-heading {
|
||||
|
||||
.table-row,
|
||||
.table-row-search {
|
||||
background-color: #ececec;
|
||||
color : #3e3e3e;
|
||||
font-weight : bold;
|
||||
}
|
||||
|
||||
.table-cell:hover {
|
||||
cursor : pointer;
|
||||
background-color: #c4afaf;
|
||||
/* box-shadow : 0px 1px 4px rgba(0, 0, 0, .08); */
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
display : flex;
|
||||
flex-flow : column wrap;
|
||||
flex : 1;
|
||||
font-size : 14px;
|
||||
padding : 8px 3px;
|
||||
justify-content: left;
|
||||
align-items : left;
|
||||
transition : all 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.table-cell:first-child {
|
||||
flex: 1 1 30%;
|
||||
}
|
||||
|
||||
.table-cell:last-child {
|
||||
flex: 0 1 7%;
|
||||
}
|
||||
|
||||
#table-cell-status,
|
||||
.table-heading-status {
|
||||
flex : 0 1 15%;
|
||||
overflow : hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.table-cell:hover {
|
||||
cursor : pointer;
|
||||
background-color: #F0F0F0;
|
||||
/* box-shadow : 0px 1px 4px rgba(0, 0, 0, .08); */
|
||||
}
|
||||
|
||||
.row-sub-container {
|
||||
display : flex;
|
||||
flex-flow: column nowrap;
|
||||
flex : 1;
|
||||
}
|
||||
|
||||
.row-sub-container .table-cell {
|
||||
padding : 8px 0;
|
||||
border-bottom: 1px solid #dadada;
|
||||
}
|
||||
|
||||
.table-row:last-child,
|
||||
.row-sub-container .table-cell:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
#ncdownloader-table-wrapper {
|
||||
#ncdownloader-form-wrapper {
|
||||
position : relative;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.table-cell:first-child {
|
||||
flex : 1 0 250px;
|
||||
overflow : hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.table-cell:last-child {
|
||||
flex: 1 1 7%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
56
img/app.svg
Normal file
56
img/app.svg
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="32"
|
||||
width="32"
|
||||
version="1"
|
||||
viewBox="0 0 32 32"
|
||||
id="svg4"
|
||||
sodipodi:docname="app.svg"
|
||||
inkscape:version="0.92.1 r">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="789"
|
||||
inkscape:window-height="480"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.375"
|
||||
inkscape:cx="-8.3389831"
|
||||
inkscape:cy="16"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="M13.733 0a.915.915 0 0 0-.933.934V3.6c-1.182.304-2.243.794-3.267 1.4L7.6 3.068a.93.93 0 0 0-1.334 0l-3.2 3.2a.93.93 0 0 0 0 1.334L5 9.535c-.607 1.024-1.097 2.085-1.4 3.267H.933a.915.915 0 0 0-.933.934v4.533c0 .53.403.934.933.934H3.6c.303 1.182.793 2.243 1.4 3.267l-1.934 1.935a.93.93 0 0 0 0 1.333l3.2 3.2a.93.93 0 0 0 1.333 0L9.532 27c1.024.61 2.085 1.097 3.266 1.4v2.667c0 .53.402.933.932.933h4.534c.53 0 .933-.403.933-.935V28.4c1.18-.305 2.24-.795 3.265-1.4L24.4 28.93a.93.93 0 0 0 1.332 0l3.2-3.2a.93.93 0 0 0 0-1.333L27 22.465c.607-1.024 1.096-2.085 1.4-3.266h2.665a.915.915 0 0 0 .935-.933v-4.534a.915.915 0 0 0-.934-.933H28.4c-.304-1.182-.792-2.243-1.4-3.267L28.932 7.6a.93.93 0 0 0 0-1.334l-3.2-3.2a.93.93 0 0 0-1.333 0L22.465 5c-1.024-.607-2.084-1.097-3.266-1.4V.933A.915.915 0 0 0 18.267 0zM16 8.87A7.134 7.134 0 0 1 23.13 16 7.134 7.134 0 0 1 16 23.133c-3.936 0-7.13-3.196-7.13-7.132S12.063 8.87 16 8.87z"
|
||||
display="block"
|
||||
fill="#fff"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
1
img/ncdownloader.svg
Normal file
1
img/ncdownloader.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg style="enable-background:new 0 0 499.968 394.332;" version="1.1" viewBox="0 0 499.968 394.332" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="cloud-arrow-down"><g id="cloud-arrow-down_1_"><path d="M404.168,119.452c-4.322,0-8.527,0.365-12.7,0.923C387.832,53.29,332.279,0,264.272,0 c-55.51,0-97.805,40.418-115.279,89.932c-9.311-2.446-24.007-8.646-34.089-8.646C51.456,81.286,0,132.709,0,196.19 c0,63.437,51.456,114.882,114.904,114.882c15.607,0,39.721,0,67.577,0l51.81,68.844c1.717,2.424,11.059,14.417,26.506,14.417 c6.908,0,17.077-2.489,26.602-14.889l49.149-68.371c30.882,0,55.896,0,67.62,0c52.904,0,95.8-42.885,95.8-95.811 C499.968,162.337,457.072,119.452,404.168,119.452z M228.327,130.693h60.831v22.569v4.956h-60.831v-4.956V130.693z M228.327,197.069v-20.66v-4.955h60.831v4.955v20.617V199h-60.831V197.069z M314.173,311.072l-41.545,57.774 c-12.4,16.111-23.533,0.514-23.533,0.514l-43.851-58.288l-28.275-37.564c-6.469-13.28,7.691-12.893,7.691-12.893h44.279v-43.443 v-1.974h60.348v1.952v43.465H334.8c0,0,10.845-0.215,7.423,11.434L314.173,311.072z" style="fill:#FFFFFF;"/></g></g><g id="Layer_1"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
img/play.svg
Normal file
3
img/play.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play" viewBox="0 0 16 16">
|
||||
<path d="M10.804 8 5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 299 B |
4
img/power.svg
Normal file
4
img/power.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-power" viewBox="0 0 16 16">
|
||||
<path d="M7.5 1v7h1V1h-1z"/>
|
||||
<path d="M3 8.812a4.999 4.999 0 0 1 2.578-4.375l-.485-.874A6 6 0 1 0 11 3.616l-.501.865A5 5 0 1 1 3 8.812z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 273 B |
4
img/trash.svg
Normal file
4
img/trash.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 573 B |
4
img/trash.svg:Zone.Identifier
Normal file
4
img/trash.svg:Zone.Identifier
Normal file
@@ -0,0 +1,4 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
ReferrerUrl=https://icons.getbootstrap.com/icons/trash/
|
||||
HostUrl=https://icons.getbootstrap.com/assets/icons/trash.svg
|
||||
26
js/settings/admin.js
Normal file
26
js/settings/admin.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* ownCloud - ocDownloader
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the LICENSE file.
|
||||
*
|
||||
* @author Xavier Beurois <www.sgc-univ.net>
|
||||
* @copyright Xavier Beurois 2015
|
||||
*/
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
$('.ncdownloader-admin-settings').on('click', 'input[type="button"]', function (event) {
|
||||
OC.msg.startSaving('#ncdownloader-message');
|
||||
const target = $(this).attr("data-rel");
|
||||
const http = $.ncdownloader.http;
|
||||
let inputData = http.getData(target);
|
||||
http.setData(inputData.data);
|
||||
const path = inputData.url || "/apps/ncdownloader/admin/save";
|
||||
let url = OC.generateUrl(path);
|
||||
http.setUrl(url);
|
||||
http.setHandler(function () {
|
||||
OC.msg.finishedSuccess('#ncdownloader-message', "OK");
|
||||
});
|
||||
http.send();
|
||||
});
|
||||
});
|
||||
20
js/settings/personal.js
Normal file
20
js/settings/personal.js
Normal file
@@ -0,0 +1,20 @@
|
||||
+(function ($) {
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
$('.ncdownloader-personal-settings').on('click', 'input[type="button"]', function (event) {
|
||||
OC.msg.startSaving('#ncdownloader-message');
|
||||
const target = $(this).attr("data-rel");
|
||||
const http = $.ncdownloader.http;
|
||||
let inputData = http.getData(target);
|
||||
http.setData(inputData.data);
|
||||
const path = inputData.url || "/apps/ncdownloader/personal/save";
|
||||
let url = OC.generateUrl(path);
|
||||
http.setUrl(url);
|
||||
http.setHandler(function (data) {
|
||||
OC.msg.finishedSuccess('#ncdownloader-message', "OK");
|
||||
});
|
||||
http.send();
|
||||
});
|
||||
});
|
||||
}
|
||||
)(jQuery)
|
||||
1
l10n/.gitkeep
Normal file
1
l10n/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
165
l10n/zh_CN.js
Normal file
165
l10n/zh_CN.js
Normal file
@@ -0,0 +1,165 @@
|
||||
OC.L10N.register(
|
||||
"ncdownloader",
|
||||
{
|
||||
"Unable to find YouTube-DL binary" : "找不到YouTube-DL可执行程序",
|
||||
"Invalid proxy address URL" : "无效的代理服务器地址",
|
||||
"Proxy port should be a numeric value" : "代理服务器端口应为数字形式",
|
||||
"Proxy port should be a value from 1 to 65536" : "代理服务器端口应在1至65536之间",
|
||||
"Invalid Aria address URL" : "无效的 Aria 地址URL",
|
||||
"Aria port should be a numeric value" : "Aria 端口应该是一个数值",
|
||||
"Aria port should be a value from 1 to 65536" : "Aria 端口应该是一个从1到65536的整数值",
|
||||
"Max download speed setting should be a numeric value" : "最大下载速度设置值应为数字形式",
|
||||
"BitTorrent protocol max upload speed setting should be a numeric value" : "BitTorrent协议的最大上传速度设置应为数字形式",
|
||||
"Saved" : "已保存",
|
||||
"You are not allowed to use the BitTorrent protocol" : "您无权使用BitTorrent协议",
|
||||
"Download started" : "开始下载",
|
||||
"Seeders" : "种子源",
|
||||
"N/A" : "N/A",
|
||||
"Returned GID is null ! Is Aria2c running as a daemon ?" : "返回GID为空!Aria2c是否以服务方式运行?",
|
||||
"Please check the URL or filepath you've just provided" : "请检查您刚提供的URL或文件路径",
|
||||
"Error while uploading torrent file" : "上传种子时文件发生错误",
|
||||
"Upload OK" : "上传完成",
|
||||
"You are not allowed to use the FTP protocol" : "您无权使用 FTP 协议",
|
||||
"Please check the URL you've just provided" : "请检查您刚提供的URL",
|
||||
"You are not allowed to use the HTTP protocol" : "您无权使用HTTP协议",
|
||||
"Video" : "视频",
|
||||
"The folder doesn't exist. It has been created." : "此文件夹不存在。它已被创建。",
|
||||
"Unknown field" : "未知的字段",
|
||||
"Undefined field" : "未定义的字段",
|
||||
"Uploaded" : "已上传",
|
||||
"Ratio" : "比例",
|
||||
"Removed" : "已移除",
|
||||
"Seeding" : "做种中",
|
||||
"Returned status is null ! Is Aria2c running as a daemon ?" : "返回状态为空!Aria2c是否以服务方式运行?",
|
||||
"Unable to find download status file %s" : "无法找到下载任务状态文件%s",
|
||||
"The download has been paused" : "此下载任务已被暂停",
|
||||
"An error occurred while pausing the download" : "暂停下载任务时出错",
|
||||
"Bad GID" : "错误的GID",
|
||||
"The download has been unpaused" : "此下载任务已解除暂停状态",
|
||||
"An error occurred while unpausing the download" : "解除下载任务的暂停状态时出错",
|
||||
"The download has been cleaned" : "此下载任务已被清理",
|
||||
"All downloads have been cleaned" : "所有下载任务已被清理",
|
||||
"No GIDS in the download queue" : "在下载任务队列中没有GIDS",
|
||||
"The download has been removed" : "此下载任务已被移除",
|
||||
"An error occurred while removing the download" : "移除下载任务时出错",
|
||||
"All downloads have been removed" : "所有下载任务已被移除",
|
||||
"The download has been totally removed" : "此下载任务已被完全移除",
|
||||
"You are not allowed to use the YouTube protocol" : "您无权使用YouTube协议",
|
||||
"Unable to retrieve true YouTube audio URL" : "无法获取真实的YouTube音频URL",
|
||||
"Unable to retrieve true YouTube video URL" : "无法获取真实的YouTube视频URL",
|
||||
"Invalid URL. Please check the address of the file…" : "非法的URL。请检查文件地址 ...",
|
||||
"Select a file.torrent" : "选择torrent种子文件",
|
||||
"Unable to find the GID for this download…" : "无法找到此下载任务的GID ...",
|
||||
"No downloads in the queue…" : "在队列中无下载任务 ...",
|
||||
"Paused" : "已暂停",
|
||||
"Active" : "活动的",
|
||||
"No Torrent Files" : "无torrent文件",
|
||||
"Upload" : "上传",
|
||||
"ocDownloader" : "ocDownloader",
|
||||
"Easy to use download manager for Nextcloud" : "易用的Nextcloud下载管理器",
|
||||
"Easy to use download manager using Curl/Aria2 and youtube-dl to allow downloading HTTP(S), FTP(S), YouTube videos and BitTorrent files. For more information on how to install, please go to https://github.com/e-alfred/ncdownloader/blob/master/README.md" : "易用的下载管理器,使用Curl/Aria2和youtube-dl工具来下载HTTP(S)、FTP(S)、YouTube视频和BitTorrent文件。关于如何安装的更多信息,请访问 https://github.com/e-alfred/ncdownloader/blob/master/README.md",
|
||||
"Active Downloads" : "活动的下载任务",
|
||||
" using <strong>%s</strong>" : "正在使用<strong>%s</strong>",
|
||||
"FILENAME" : "文件名",
|
||||
"PROTOCOL" : "协议",
|
||||
"INFORMATION" : "信息",
|
||||
"SPEED" : "速度",
|
||||
"Loading" : "正在加载",
|
||||
"Magnet/HTTP" : "Magnet/HTTP 协议",
|
||||
"HTTP" : "HTTP 协议",
|
||||
"Add Download" : "添加下载任务",
|
||||
"New Magnet/HTTP download" : "新的 Magnet/HTTP 下载",
|
||||
"New HTTP download" : "新HTTP下载任务",
|
||||
"Launch Magnet/HTTP download" : "启动 Magnet/HTTP 下载",
|
||||
"Launch HTTP download" : "启动 HTTP 下载",
|
||||
"Magnet/HTTP link to download" : "要下载的 Magnet/HTTP 链接",
|
||||
"HTTP link to download" : "要下载的 HTTP 链接",
|
||||
"Options" : "选项",
|
||||
"Basic Auth User" : "Basic Auth 用户",
|
||||
"Username" : "用户名",
|
||||
"Basic Auth Password" : "Basic Auth 密码",
|
||||
"Password" : "密码",
|
||||
"HTTP Referer" : "HTTP Referer",
|
||||
"Referer" : "Referer",
|
||||
"HTTP User Agent" : "HTTP User Agent",
|
||||
"Useragent" : "Useragent",
|
||||
"HTTP Output Filename" : "HTTP输出文件名",
|
||||
"Filename" : "文件名",
|
||||
"New FTP download" : "新FTP下载任务",
|
||||
"Launch FTP Download" : "启动FTP下载任务",
|
||||
"FTP URL to download" : "要下载的FTP URL",
|
||||
"FTP User" : "FTP 用户",
|
||||
"FTP Password" : "FTP 密码",
|
||||
"FTP Referer" : "FTP Referer",
|
||||
"FTP User Agent" : "FTP User Agent",
|
||||
"FTP Output Filename" : "FTP输出文件名",
|
||||
"Passive Mode" : "被动模式",
|
||||
"New YouTube download" : "新YouTube下载任务",
|
||||
"Launch YouTube Download" : "启动YouTube下载任务",
|
||||
"YouTube Video URL to download" : "要下载的YouTube视频URL",
|
||||
"Only Extract audio ?" : "仅提取音频?",
|
||||
"(No post-processing, just extract the best audio quality)" : "(无后期处理,按最佳音质提取)",
|
||||
"Force IPv4 ?" : "强制使用IPv4?",
|
||||
"New BitTorrent download" : "新BitTorrent下载任务",
|
||||
"Launch BitTorrent Download" : "启动BitTorrent下载任务",
|
||||
"(Default : List torrent files in the folder /Downloads/Files/Torrents, go to the Personal Settings panel)" : "(默认:列出文件夹/Downloads/Files/Torrents中的torrent文件,请到个人设置面板设置)",
|
||||
"Remove torrent file ?" : "移除torrent文件?",
|
||||
"STATUS" : "状态",
|
||||
"All Downloads" : "所有下载任务",
|
||||
"Complete Downloads" : "已完成的下载任务",
|
||||
"Waiting Downloads" : "正在等待的下载任务",
|
||||
"Stopped Downloads" : "已停止的下载任务",
|
||||
"Removed Downloads" : "已移除的下载任务",
|
||||
"Leave fields blank to reset a setting value" : "将字段留空以重置设置值",
|
||||
"YouTube DL Binary Path" : "YouTube DL 程序路径",
|
||||
"YouTube DL Audio Format" : "YouTube DL 音频格式",
|
||||
"YouTube DL Video Format" : "YouTube DL 视频格式",
|
||||
"Proxy settings" : "代理设置",
|
||||
"Proxy Address" : "代理地址",
|
||||
"Proxy Port" : "代理端口",
|
||||
"If no authentication is required by your proxy, leave the following fields blank" : "如果您的代理不需要验证,请将以下字段留空",
|
||||
"Proxy User" : "代理用户",
|
||||
"Proxy Password" : "代理密码",
|
||||
"Only use proxy settings with YouTube-DL?" : "仅对YouTube-DL使用代理设置?",
|
||||
"No" : "否",
|
||||
"Yes" : "是",
|
||||
"General settings" : "通用设置",
|
||||
"WARNING !! Switching from ARIA2 to another downloader engine will remove all current downloads from ARIA2" : "警告!从ARIA2切换到其他下载器引擎将会移除ARIA2中的所有当前下载任务",
|
||||
"Which downloader do you want to use?" : "您想使用哪个下载器?",
|
||||
"Available protocols" : "可选协议",
|
||||
"ARIA2 Address" : "ARIA2 地址",
|
||||
"ARIA2 Port" : "ARIA2 端口",
|
||||
"ARIA2 Secret Token" : "ARIA2 秘密令牌",
|
||||
"Secret Token" : "秘密令牌",
|
||||
"Max download speed ?" : "最大下载速度?",
|
||||
"KB/s (empty or 0 : unlimited, default : unlimited)" : "KB/s(空或0:无限制,默认:无限制)",
|
||||
"BitTorrent protocol settings - Max upload speed" : "BitTorrent协议设置 - 最大上传速度",
|
||||
"BitTorrent protocol max upload speed?" : "BitTorrent协议最大上传速度?",
|
||||
"Allow protocols for users (except for members of the admin group)" : "用户允许使用的协议(管理员组成员除外)",
|
||||
"Allow HTTP/Magnet?" : "允许 HTTP/Magnet?",
|
||||
"Allow FTP?" : "允许 FTP 吗?",
|
||||
"Allow YouTube?" : "允许 YouTube 吗?",
|
||||
"Allow BitTorrent?" : "允许 BitTorrent 吗?",
|
||||
"Default Downloads Folder" : "默认下载文件夹",
|
||||
"Save" : "保存",
|
||||
"Default Torrents Folder" : "默认Torrents文件夹",
|
||||
"BitTorrent protocol settings - Ratio" : "BitTorrent协议设置 - 比例",
|
||||
"Default ratio to reach?" : "要达到的默认比例?",
|
||||
"unlimited" : "无限制",
|
||||
"BitTorrent protocol settings - Seed time" : "BitTorrent协议设置 - 做种时长",
|
||||
"Seed time to reach?" : "要达到的做种时长?",
|
||||
"minutes" : "分钟",
|
||||
"hours" : "小时",
|
||||
"days" : "天",
|
||||
"weeks" : "周",
|
||||
"months" : "月",
|
||||
"ACTIONS" : "操作",
|
||||
"years" : "年",
|
||||
"Invalid url": "无效URL",
|
||||
"Youtube-dl NOT installed!":"没有安装Youtube-dl!",
|
||||
"Speed":"速度",
|
||||
"Status":"状态",
|
||||
"Actions": "选项",
|
||||
"Progress": "进度"
|
||||
},
|
||||
"nplurals=1; plural=0;");
|
||||
163
l10n/zh_CN.json
Normal file
163
l10n/zh_CN.json
Normal file
@@ -0,0 +1,163 @@
|
||||
{ "translations": {
|
||||
"Unable to find YouTube-DL binary" : "找不到YouTube-DL可执行程序",
|
||||
"Invalid proxy address URL" : "无效的代理服务器地址",
|
||||
"Proxy port should be a numeric value" : "代理服务器端口应为数字形式",
|
||||
"Proxy port should be a value from 1 to 65536" : "代理服务器端口应在1至65536之间",
|
||||
"Invalid Aria address URL" : "无效的 Aria 地址URL",
|
||||
"Aria port should be a numeric value" : "Aria 端口应该是一个数值",
|
||||
"Aria port should be a value from 1 to 65536" : "Aria 端口应该是一个从1到65536的整数值",
|
||||
"Max download speed setting should be a numeric value" : "最大下载速度设置值应为数字形式",
|
||||
"BitTorrent protocol max upload speed setting should be a numeric value" : "BitTorrent协议的最大上传速度设置应为数字形式",
|
||||
"Saved" : "已保存",
|
||||
"You are not allowed to use the BitTorrent protocol" : "您无权使用BitTorrent协议",
|
||||
"Download started" : "开始下载",
|
||||
"Seeders" : "种子源",
|
||||
"N/A" : "N/A",
|
||||
"Returned GID is null ! Is Aria2c running as a daemon ?" : "返回GID为空!Aria2c是否以服务方式运行?",
|
||||
"Please check the URL or filepath you've just provided" : "请检查您刚提供的URL或文件路径",
|
||||
"Error while uploading torrent file" : "上传种子时文件发生错误",
|
||||
"Upload OK" : "上传完成",
|
||||
"You are not allowed to use the FTP protocol" : "您无权使用 FTP 协议",
|
||||
"Please check the URL you've just provided" : "请检查您刚提供的URL",
|
||||
"You are not allowed to use the HTTP protocol" : "您无权使用HTTP协议",
|
||||
"Video" : "视频",
|
||||
"The folder doesn't exist. It has been created." : "此文件夹不存在。它已被创建。",
|
||||
"Unknown field" : "未知的字段",
|
||||
"Undefined field" : "未定义的字段",
|
||||
"Uploaded" : "已上传",
|
||||
"Ratio" : "比例",
|
||||
"Removed" : "已移除",
|
||||
"Seeding" : "做种中",
|
||||
"Returned status is null ! Is Aria2c running as a daemon ?" : "返回状态为空!Aria2c是否以服务方式运行?",
|
||||
"Unable to find download status file %s" : "无法找到下载任务状态文件%s",
|
||||
"The download has been paused" : "此下载任务已被暂停",
|
||||
"An error occurred while pausing the download" : "暂停下载任务时出错",
|
||||
"Bad GID" : "错误的GID",
|
||||
"The download has been unpaused" : "此下载任务已解除暂停状态",
|
||||
"An error occurred while unpausing the download" : "解除下载任务的暂停状态时出错",
|
||||
"The download has been cleaned" : "此下载任务已被清理",
|
||||
"All downloads have been cleaned" : "所有下载任务已被清理",
|
||||
"No GIDS in the download queue" : "在下载任务队列中没有GIDS",
|
||||
"The download has been removed" : "此下载任务已被移除",
|
||||
"An error occurred while removing the download" : "移除下载任务时出错",
|
||||
"All downloads have been removed" : "所有下载任务已被移除",
|
||||
"The download has been totally removed" : "此下载任务已被完全移除",
|
||||
"You are not allowed to use the YouTube protocol" : "您无权使用YouTube协议",
|
||||
"Unable to retrieve true YouTube audio URL" : "无法获取真实的YouTube音频URL",
|
||||
"Unable to retrieve true YouTube video URL" : "无法获取真实的YouTube视频URL",
|
||||
"Invalid URL. Please check the address of the file…" : "非法的URL。请检查文件地址 ...",
|
||||
"Select a file.torrent" : "选择torrent种子文件",
|
||||
"Unable to find the GID for this download…" : "无法找到此下载任务的GID ...",
|
||||
"No downloads in the queue…" : "在队列中无下载任务 ...",
|
||||
"Paused" : "已暂停",
|
||||
"Active" : "活动的",
|
||||
"No Torrent Files" : "无torrent文件",
|
||||
"Upload" : "上传",
|
||||
"ocDownloader" : "ocDownloader",
|
||||
"Easy to use download manager for Nextcloud" : "易用的Nextcloud下载管理器",
|
||||
"Easy to use download manager using Curl/Aria2 and youtube-dl to allow downloading HTTP(S), FTP(S), YouTube videos and BitTorrent files. For more information on how to install, please go to https://github.com/e-alfred/ncdownloader/blob/master/README.md" : "易用的下载管理器,使用Curl/Aria2和youtube-dl工具来下载HTTP(S)、FTP(S)、YouTube视频和BitTorrent文件。关于如何安装的更多信息,请访问 https://github.com/e-alfred/ncdownloader/blob/master/README.md",
|
||||
"Active Downloads" : "活动的下载任务",
|
||||
" using <strong>%s</strong>" : "正在使用<strong>%s</strong>",
|
||||
"FILENAME" : "文件名",
|
||||
"PROTOCOL" : "协议",
|
||||
"INFORMATION" : "信息",
|
||||
"SPEED" : "速度",
|
||||
"Loading" : "正在加载",
|
||||
"Magnet/HTTP" : "Magnet/HTTP 协议",
|
||||
"HTTP" : "HTTP 协议",
|
||||
"Add Download" : "添加下载任务",
|
||||
"New Magnet/HTTP download" : "新的 Magnet/HTTP 下载",
|
||||
"New HTTP download" : "新HTTP下载任务",
|
||||
"Launch Magnet/HTTP download" : "启动 Magnet/HTTP 下载",
|
||||
"Launch HTTP download" : "启动 HTTP 下载",
|
||||
"Magnet/HTTP link to download" : "要下载的 Magnet/HTTP 链接",
|
||||
"HTTP link to download" : "要下载的 HTTP 链接",
|
||||
"Options" : "选项",
|
||||
"Basic Auth User" : "Basic Auth 用户",
|
||||
"Username" : "用户名",
|
||||
"Basic Auth Password" : "Basic Auth 密码",
|
||||
"Password" : "密码",
|
||||
"HTTP Referer" : "HTTP Referer",
|
||||
"Referer" : "Referer",
|
||||
"HTTP User Agent" : "HTTP User Agent",
|
||||
"Useragent" : "Useragent",
|
||||
"HTTP Output Filename" : "HTTP输出文件名",
|
||||
"Filename" : "文件名",
|
||||
"New FTP download" : "新FTP下载任务",
|
||||
"Launch FTP Download" : "启动FTP下载任务",
|
||||
"FTP URL to download" : "要下载的FTP URL",
|
||||
"FTP User" : "FTP 用户",
|
||||
"FTP Password" : "FTP 密码",
|
||||
"FTP Referer" : "FTP Referer",
|
||||
"FTP User Agent" : "FTP User Agent",
|
||||
"FTP Output Filename" : "FTP输出文件名",
|
||||
"Passive Mode" : "被动模式",
|
||||
"New YouTube download" : "新YouTube下载任务",
|
||||
"Launch YouTube Download" : "启动YouTube下载任务",
|
||||
"YouTube Video URL to download" : "要下载的YouTube视频URL",
|
||||
"Only Extract audio ?" : "仅提取音频?",
|
||||
"(No post-processing, just extract the best audio quality)" : "(无后期处理,按最佳音质提取)",
|
||||
"Force IPv4 ?" : "强制使用IPv4?",
|
||||
"New BitTorrent download" : "新BitTorrent下载任务",
|
||||
"Launch BitTorrent Download" : "启动BitTorrent下载任务",
|
||||
"(Default : List torrent files in the folder /Downloads/Files/Torrents, go to the Personal Settings panel)" : "(默认:列出文件夹/Downloads/Files/Torrents中的torrent文件,请到个人设置面板设置)",
|
||||
"Remove torrent file ?" : "移除torrent文件?",
|
||||
"STATUS" : "状态",
|
||||
"All Downloads" : "所有下载任务",
|
||||
"Complete Downloads" : "已完成的下载任务",
|
||||
"Waiting Downloads" : "正在等待的下载任务",
|
||||
"Stopped Downloads" : "已停止的下载任务",
|
||||
"Removed Downloads" : "已移除的下载任务",
|
||||
"Leave fields blank to reset a setting value" : "将字段留空以重置设置值",
|
||||
"YouTube DL Binary Path" : "YouTube DL 程序路径",
|
||||
"YouTube DL Audio Format" : "YouTube DL 音频格式",
|
||||
"YouTube DL Video Format" : "YouTube DL 视频格式",
|
||||
"Proxy settings" : "代理设置",
|
||||
"Proxy Address" : "代理地址",
|
||||
"Proxy Port" : "代理端口",
|
||||
"If no authentication is required by your proxy, leave the following fields blank" : "如果您的代理不需要验证,请将以下字段留空",
|
||||
"Proxy User" : "代理用户",
|
||||
"Proxy Password" : "代理密码",
|
||||
"Only use proxy settings with YouTube-DL?" : "仅对YouTube-DL使用代理设置?",
|
||||
"No" : "否",
|
||||
"Yes" : "是",
|
||||
"General settings" : "通用设置",
|
||||
"WARNING !! Switching from ARIA2 to another downloader engine will remove all current downloads from ARIA2" : "警告!从ARIA2切换到其他下载器引擎将会移除ARIA2中的所有当前下载任务",
|
||||
"Which downloader do you want to use?" : "您想使用哪个下载器?",
|
||||
"Available protocols" : "可选协议",
|
||||
"ARIA2 Address" : "ARIA2 地址",
|
||||
"ARIA2 Port" : "ARIA2 端口",
|
||||
"ARIA2 Secret Token" : "ARIA2 秘密令牌",
|
||||
"Secret Token" : "秘密令牌",
|
||||
"Max download speed ?" : "最大下载速度?",
|
||||
"KB/s (empty or 0 : unlimited, default : unlimited)" : "KB/s(空或0:无限制,默认:无限制)",
|
||||
"BitTorrent protocol settings - Max upload speed" : "BitTorrent协议设置 - 最大上传速度",
|
||||
"BitTorrent protocol max upload speed?" : "BitTorrent协议最大上传速度?",
|
||||
"Allow protocols for users (except for members of the admin group)" : "用户允许使用的协议(管理员组成员除外)",
|
||||
"Allow HTTP/Magnet?" : "允许 HTTP/Magnet?",
|
||||
"Allow FTP?" : "允许 FTP 吗?",
|
||||
"Allow YouTube?" : "允许 YouTube 吗?",
|
||||
"Allow BitTorrent?" : "允许 BitTorrent 吗?",
|
||||
"Default Downloads Folder" : "默认下载文件夹",
|
||||
"Save" : "保存",
|
||||
"Default Torrents Folder" : "默认Torrents文件夹",
|
||||
"BitTorrent protocol settings - Ratio" : "BitTorrent协议设置 - 比例",
|
||||
"Default ratio to reach?" : "要达到的默认比例?",
|
||||
"unlimited" : "无限制",
|
||||
"BitTorrent protocol settings - Seed time" : "BitTorrent协议设置 - 做种时长",
|
||||
"Seed time to reach?" : "要达到的做种时长?",
|
||||
"minutes" : "分钟",
|
||||
"hours" : "小时",
|
||||
"days" : "天",
|
||||
"weeks" : "周",
|
||||
"months" : "月",
|
||||
"ACTIONS" : "操作",
|
||||
"years" : "年",
|
||||
"Invalid url": "无效URL",
|
||||
"Youtube-dl NOT installed!":"没有安装Youtube-dl!",
|
||||
"Speed":"速度",
|
||||
"Status":"状态",
|
||||
"Actions": "选项",
|
||||
"Progress": "进度"
|
||||
},"pluralForm" :"nplurals=1; plural=0;"
|
||||
}
|
||||
96
lib/Command/Aria2Command.php
Normal file
96
lib/Command/Aria2Command.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\NcDownloader\Command;
|
||||
|
||||
use OCA\NcDownloader\Tools\Aria2;
|
||||
use OCA\NcDownloader\Tools\DBConn;
|
||||
use OCA\NcDownloader\Tools\File;
|
||||
use OCA\NcDownloader\Tools\Helper;
|
||||
use OC\Core\Command\Base;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Aria2Command extends base
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->conn = new DBConn();
|
||||
$this->aria2 = new Aria2();
|
||||
parent::__construct();
|
||||
}
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('aria2')
|
||||
->setDescription('Aria2 hooks')
|
||||
->addArgument(
|
||||
'action',
|
||||
InputArgument::OPTIONAL,
|
||||
'Aria2 hook names: start,complete,error'
|
||||
)->addOption(
|
||||
'gid',
|
||||
'g',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Aria2 gid'
|
||||
)->addOption(
|
||||
'path',
|
||||
'p',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Downloaded file path',
|
||||
)->addOption(
|
||||
'number',
|
||||
'N',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Number of Files',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
|
||||
if (!$action = $input->getArgument('action')) {
|
||||
$action = 'start';
|
||||
}
|
||||
$gid = $input->getOption('gid');
|
||||
$path = $input->getOption('path');
|
||||
$numFile = $input->getOption('number');
|
||||
if (!$gid) {
|
||||
return 1;
|
||||
}
|
||||
$parent_gid = $this->aria2->getFollowingGid($gid); // $this->conn->getAll();
|
||||
if ($parent_gid) {
|
||||
$tablename = $this->conn->queryBuilder->getTableName("ncdownloader_info");
|
||||
$sql = sprintf("UPDATE %s set followedby = ? WHERE gid = ?", $tablename);
|
||||
// $data = serialize(['followedby' => "82140bd962946ae0"]);
|
||||
$this->conn->execute($sql, [$gid, $parent_gid]);
|
||||
}
|
||||
|
||||
$result = $this->conn->getByGid($parent_gid);
|
||||
//$data = unserialize($result['data']);
|
||||
$output->writeln(print_r($result, true));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
220
lib/Controller/Aria2Controller.php
Normal file
220
lib/Controller/Aria2Controller.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
namespace OCA\NcDownloader\Controller;
|
||||
|
||||
use OCA\NcDownloader\Tools\Aria2;
|
||||
use OCA\NcDownloader\Tools\DBConn;
|
||||
use OCA\NcDownloader\Tools\File;
|
||||
use OCA\NcDownloader\Tools\Helper;
|
||||
use OCA\NcDownloader\Tools\Settings;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OC_Util;
|
||||
use \OC\Files\Filesystem;
|
||||
|
||||
class Aria2Controller extends Controller
|
||||
{
|
||||
private $userId;
|
||||
private $settings = null;
|
||||
//@config OC\AppConfig
|
||||
private $config;
|
||||
private $aria2Opts;
|
||||
private $l10n;
|
||||
|
||||
public function __construct($appName, IRequest $request, $UserId, IL10N $IL10N, IRootFolder $rootFolder, Aria2 $aria2)
|
||||
{
|
||||
parent::__construct($appName, $request);
|
||||
$this->appName = $appName;
|
||||
$this->uid = $UserId;
|
||||
$this->l10n = $IL10N;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->urlGenerator = \OC::$server->getURLGenerator();
|
||||
$this->settings = new Settings($UserId);
|
||||
$this->downloadDir = $this->settings->get('ncd_downloader_dir') ?? "/Downloads";
|
||||
OC_Util::setupFS();
|
||||
//$this->config = \OC::$server->getAppConfig();
|
||||
|
||||
$this->aria2 = $aria2;
|
||||
|
||||
$this->aria2->init();
|
||||
$this->dbconn = new DBConn();
|
||||
}
|
||||
|
||||
public function Action($path)
|
||||
{
|
||||
$path = strtolower(trim($path));
|
||||
|
||||
if (!in_array($path, ['start', 'check']) && !($gid = $this->request->getParam('gid'))) {
|
||||
return new JSONResponse(['error' => "no gid value is received!"]);
|
||||
}
|
||||
switch (strtolower($path)) {
|
||||
case "check":
|
||||
$resp = $this->aria2->isRunning();
|
||||
break;
|
||||
case "start":
|
||||
$resp = $this->Start();
|
||||
break;
|
||||
case "pause":
|
||||
$resp = $this->aria2->pause($gid);
|
||||
break;
|
||||
case "remove":
|
||||
$resp = $this->aria2->remove($gid);
|
||||
break;
|
||||
case "unpause":
|
||||
$resp = $this->aria2->unpause($gid);
|
||||
break;
|
||||
case "get":
|
||||
$resp = $this->aria2->tellStatus($gid);
|
||||
break;
|
||||
case 'purge':
|
||||
$resp = $this->aria2->removeDownloadResult($gid);
|
||||
}
|
||||
return new JSONResponse($resp);
|
||||
}
|
||||
private function Start()
|
||||
{
|
||||
if ($this->aria2->isRunning()) {
|
||||
$data = ['status' => (bool) $this->aria2->stop()];
|
||||
return $data;
|
||||
}
|
||||
$data = $this->aria2->start();
|
||||
return $data;
|
||||
}
|
||||
public function Update()
|
||||
{
|
||||
File::syncFolder($this->downloadDir);
|
||||
return new JSONResponse([]);
|
||||
}
|
||||
|
||||
private function createActionItem($name, $path)
|
||||
{
|
||||
return array(
|
||||
'name' => $name,
|
||||
'path' => $this->urlGenerator->linkToRoute('ncdownloader.Aria2.Action', ['path' => $path]),
|
||||
);
|
||||
}
|
||||
public function getStatus($path)
|
||||
{
|
||||
//$path = $this->request->getRequestUri();
|
||||
$counter = $this->aria2->getCounters();
|
||||
switch (strtolower($path)) {
|
||||
case "active":
|
||||
$resp = $this->aria2->tellActive();
|
||||
break;
|
||||
case "waiting":
|
||||
$resp = $this->aria2->tellWaiting([0, 999]);
|
||||
break;
|
||||
case "complete":
|
||||
$resp = $this->aria2->tellStopped([0, 999]);
|
||||
break;
|
||||
case "fail":
|
||||
$resp = $this->aria2->tellFail([0, 999]);
|
||||
break;
|
||||
default:
|
||||
$resp = $this->aria2->tellActive();
|
||||
}
|
||||
if (isset($resp['error'])) {
|
||||
return new JSONResponse($resp);
|
||||
}
|
||||
$data = $this->prepareResp($resp);
|
||||
$data['counter'] = $counter;
|
||||
return new JSONResponse($data);
|
||||
}
|
||||
private function prepareResp($resp)
|
||||
{
|
||||
|
||||
$data = [];
|
||||
if (empty($resp)) {
|
||||
return $data;
|
||||
}
|
||||
$data['row'] = [];
|
||||
|
||||
foreach ($resp as $value) {
|
||||
|
||||
$gid = $value['following'] ?? $value['gid'];
|
||||
if ($row = $this->dbconn->getByGid($gid)) {
|
||||
$filename = $row['filename'];
|
||||
$timestamp = $row['timestamp'];
|
||||
} else if (isset($value['files'][0]['path'])) {
|
||||
$parts = explode("/", ($path = $value['files'][0]['path']));
|
||||
if (count($parts) > 1) {
|
||||
$filename = basename(dirname($path));
|
||||
} else {
|
||||
$filename = basename($path);
|
||||
}
|
||||
} else {
|
||||
$filename = "Unknown";
|
||||
}
|
||||
if (!isset($value['completedLength'])) {
|
||||
continue;
|
||||
}
|
||||
//internal nextcloud absolute path for nodeExists
|
||||
//$file = $this->userFolder . $this->downloadDir . "/" . $filename;
|
||||
// $dir = $this->rootFolder->nodeExists($file) ? $this->downloadDir . "/" . $filename : $this->downloadDir;
|
||||
$file = $this->downloadDir . "/" . $filename;
|
||||
$params = ['dir' => $this->downloadDir];
|
||||
$fileInfo = Filesystem::getFileInfo($file);
|
||||
if ($fileInfo) {
|
||||
$fileType = $fileInfo->getType();
|
||||
if ($fileType === "dir") {
|
||||
$params = ['dir' => $file];
|
||||
}
|
||||
}
|
||||
$folderLink = $this->urlGenerator->linkToRoute('files.view.index', $params);
|
||||
//$peers = ($this->getPeers($info['gid']));
|
||||
$completed = Helper::formatBytes($value['completedLength']);
|
||||
$percentage = $value['completedLength'] ? 100 * ($value['completedLength'] / $value['totalLength']) : 0;
|
||||
$completed = Helper::formatBytes($value['completedLength']);
|
||||
|
||||
$total = Helper::formatBytes($value['totalLength']);
|
||||
|
||||
$remaining = (int) $value['totalLength'] - (int) $value['completedLength'];
|
||||
$remaining = ($value['downloadSpeed'] > 0) ? ($remaining / $value['downloadSpeed']) : 0;
|
||||
$left = Helper::formatInterval($remaining);
|
||||
|
||||
$numSeeders = $value['numSeeders'] ?? 0;
|
||||
$extraInfo = "Seeders: $numSeeders";
|
||||
// $numPeers = isset($peers['result']) ? count($peers['result']) : 0;
|
||||
$value['progress'] = array(sprintf("%s(%.2f%%)", $completed, $percentage), $extraInfo);
|
||||
$timestamp = $timestamp ?? 0;
|
||||
//$prefix = $value['files'][0]['path'];
|
||||
$filename = sprintf('<a class="download-file-folder" href="%s">%s</a>', $folderLink, $filename);
|
||||
$fileInfo = sprintf("%s | %s", $total, date("Y-m-d H:i:s", $timestamp));
|
||||
|
||||
$tmp = [];
|
||||
$actions = [];
|
||||
if ($this->aria2->methodName === "tellStopped") {
|
||||
$actions[] = $this->createActionItem('purge', 'purge');
|
||||
} else {
|
||||
$actions[] = $this->createActionItem('delete', 'remove');
|
||||
}
|
||||
if ($this->aria2->methodName === "tellWaiting") {
|
||||
$actions[] = $this->createActionItem('unpause', 'unpause');
|
||||
}
|
||||
$tmp['filename'] = array($filename, $fileInfo);
|
||||
if ($this->aria2->methodName === "tellActive") {
|
||||
$speed = [Helper::formatBytes($value['downloadSpeed']), $left . " left"];
|
||||
$tmp['speed'] = $speed;
|
||||
$tmp['progress'] = $value['progress'];
|
||||
$actions[] = $this->createActionItem('pause', 'pause');
|
||||
}
|
||||
if (strtolower($value['status']) === 'error') {
|
||||
$tmp['status'] = $value['errorMessage'];
|
||||
} else if ($this->aria2->methodName !== "tellActive") {
|
||||
$tmp['status'] = $value['status'];
|
||||
}
|
||||
$tmp['data_gid'] = $value['gid'] ?? 0;
|
||||
$tmp['actions'] = $actions;
|
||||
//$tmp['actions'] = '';
|
||||
array_push($data['row'], $tmp);
|
||||
}
|
||||
if ($this->aria2->methodName === "tellActive") {
|
||||
$data['title'] = Helper::getTableTitles('active');
|
||||
} else {
|
||||
$data['title'] = Helper::getTableTitles();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
135
lib/Controller/MainController.php
Normal file
135
lib/Controller/MainController.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Controller;
|
||||
|
||||
use OCA\NcDownloader\Search\torrentSearch;
|
||||
use OCA\NcDownloader\Tools\Aria2;
|
||||
use OCA\NcDownloader\Tools\DBConn;
|
||||
use OCA\NcDownloader\Tools\Helper;
|
||||
use OCA\NcDownloader\Tools\YouTube;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OC_Util;
|
||||
use \OCP\AppFramework\Http\StrictContentSecurityPolicy;
|
||||
|
||||
class MainController extends Controller
|
||||
{
|
||||
|
||||
private $settings = null;
|
||||
//@config OC\AppConfig
|
||||
private $config;
|
||||
private $aria2Opts;
|
||||
private $l10n;
|
||||
|
||||
public function __construct($appName, IRequest $request, $UserId, IL10N $IL10N, IRootFolder $rootFolder, Aria2 $aria2)
|
||||
{
|
||||
parent::__construct($appName, $request);
|
||||
$this->appName = $appName;
|
||||
$this->uid = $UserId;
|
||||
$this->l10n = $IL10N;
|
||||
$this->rootFolder = $rootFolder;
|
||||
OC_Util::setupFS();
|
||||
$this->urlGenerator = \OC::$server->getURLGenerator();
|
||||
$this->aria2 = $aria2;
|
||||
$this->aria2->init();
|
||||
$this->youtube = new Youtube();
|
||||
$this->dbconn = new DBConn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function Index()
|
||||
{
|
||||
// $str = \OC::$server->getDatabaseConnection()->getInner()->getPrefix();
|
||||
//$config = \OC::$server->getAppConfig();
|
||||
OC_Util::addScript($this->appName, 'app');
|
||||
OC_Util::addStyle($this->appName, 'style');
|
||||
OC_Util::addStyle($this->appName, 'table');
|
||||
$params = array();
|
||||
$params['aria2_running'] = $this->aria2->isRunning();
|
||||
$params['aria2_installed'] = $this->aria2->isInstalled();
|
||||
$params['youtube_installed'] = $this->youtube->isInstalled();
|
||||
|
||||
$response = new TemplateResponse($this->appName, 'Index', $params);
|
||||
|
||||
$csp = new StrictContentSecurityPolicy();
|
||||
$csp->allowEvalScript();
|
||||
$csp->allowInlineStyle();
|
||||
|
||||
$response->setContentSecurityPolicy($csp);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function newDownload()
|
||||
{
|
||||
$params = array();
|
||||
$inputValue = trim($this->request->getParam('form_input_text'));
|
||||
$type = trim($this->request->getParam('type'));
|
||||
if ($type == 'ytdl') {
|
||||
$yt = $this->youtube;
|
||||
if (!$yt->isInstalled()) {
|
||||
try {
|
||||
$filename = Helper::getFileName($yt->installUrl());
|
||||
$this->aria2->setDownloadDir($this->dataDir . "/bin");
|
||||
$resp = $this->Save($yt->installUrl(), $filename);
|
||||
return new JSONResponse($resp);
|
||||
} catch (\Exception $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
return new JSONResponse(['error' => $this->l10n->t("Youtube-dl NOT installed!")]);
|
||||
}
|
||||
if (Helper::isGetUrlSite($inputValue)) {
|
||||
if ($data = $yt->forceIPV4()->getDownloadUrl($inputValue)) {
|
||||
$this->Save($data['url'], $data['filename']);
|
||||
return new JSONResponse(['yt' => $data]);
|
||||
} else {
|
||||
return new JSONResponse(['error' => $this->l10n->t("failed to get any url!")]);
|
||||
}
|
||||
} else {
|
||||
$yt->setDownloadDir($this->realDownloadDir);
|
||||
return new JSONResponse(['yt' => $yt->download($inputValue)]);
|
||||
}
|
||||
} else if ($type === 'search') {
|
||||
$data = torrentSearch::go($inputValue);
|
||||
$resp['title'] = ['title', 'seeders', 'info', 'actions'];
|
||||
$resp['row'] = $data;
|
||||
return new JSONResponse($resp);
|
||||
}
|
||||
|
||||
$filename = Helper::getFileName($inputValue);
|
||||
$resp = $this->Save($inputValue, $filename);
|
||||
return new JSONResponse($resp);
|
||||
}
|
||||
|
||||
private function Save($url, $filename = null)
|
||||
{
|
||||
if (isset($filename)) {
|
||||
$this->aria2->setFileName($filename);
|
||||
}
|
||||
//$this->aria2->setDownloadDir("/tmp/downloads");
|
||||
$result = $this->aria2->addUri([$url]);
|
||||
$gid = $result['result'];
|
||||
if (!is_string($gid)) {
|
||||
return ['error' => 'Failed to add download task! ' . $result['error']];
|
||||
} else {
|
||||
$data = [
|
||||
'uid' => $this->uid,
|
||||
'gid' => $gid,
|
||||
'type' => 1,
|
||||
'filename' => $filename ?? 'unknown',
|
||||
'timestamp' => time(),
|
||||
'data' => serialize(['link' => $url]),
|
||||
];
|
||||
$this->dbconn->save($data);
|
||||
}
|
||||
return ['gid' => $gid, 'file' => $filename, 'result' => $gid];
|
||||
}
|
||||
}
|
||||
83
lib/Controller/SettingsController.php
Normal file
83
lib/Controller/SettingsController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Controller;
|
||||
|
||||
use OCA\NcDownloader\Tools\Helper;
|
||||
use OCA\NcDownloader\Tools\Settings;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\IRequest;
|
||||
use OC_Util;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
/*@ OC\AppFramework\Http\Request*/
|
||||
//private $request;
|
||||
|
||||
//@config OC\AppConfig
|
||||
private $config;
|
||||
public function __construct($AppName, IRequest $Request, $UserId) //, IL10N $L10N)
|
||||
|
||||
{
|
||||
parent::__construct($AppName, $Request);
|
||||
$this->UserId = $UserId;
|
||||
//$this->L10N = $L10N;
|
||||
$this->settings = new Settings($UserId);
|
||||
//$this->config = \OC::$server->getAppConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function personal()
|
||||
{
|
||||
$params = $this->request->getParams();
|
||||
foreach ($params as $key => $value) {
|
||||
if (substr($key, 0, 1) == '_') {
|
||||
continue;
|
||||
}
|
||||
$this->save($key, $value);
|
||||
}
|
||||
}
|
||||
public function aria2Get()
|
||||
{
|
||||
$data = json_decode($this->settings->get("custom_aria2_settings"));
|
||||
return new JSONResponse($data);
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
$this->settings->setType($this->settings::SYSTEM);
|
||||
$params = $this->request->getParams();
|
||||
foreach ($params as $key => $value) {
|
||||
if (substr($key, 0, 1) == '_') {
|
||||
continue;
|
||||
}
|
||||
$this->save($key, $value);
|
||||
}
|
||||
|
||||
}
|
||||
public function aria2Save()
|
||||
{
|
||||
$params = $this->request->getParams();
|
||||
$data = Helper::filterData($params, Helper::aria2Options());
|
||||
$this->settings->save("custom_aria2_settings", json_encode($data));
|
||||
}
|
||||
public function aria2Delete()
|
||||
{
|
||||
$saved = json_decode($this->settings->get("custom_aria2_settings"),1);
|
||||
$params = $this->request->getParams();
|
||||
$data = Helper::filterData($params, Helper::aria2Options());
|
||||
foreach ($data as $key => $value) {
|
||||
unset($saved[$key]);
|
||||
}
|
||||
$this->settings->save("custom_aria2_settings", json_encode($saved));
|
||||
return new JSONResponse($saved);
|
||||
}
|
||||
public function save($key, $value)
|
||||
{
|
||||
$this->settings->save($key, $value);
|
||||
}
|
||||
|
||||
}
|
||||
95
lib/Migration/Version00001date20210807.php
Normal file
95
lib/Migration/Version00001date20210807.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace OCA\NcDownloader\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
/**
|
||||
* Auto-generated migration step: Please modify to your needs!
|
||||
*/
|
||||
class Version00001date20210807 extends SimpleMigrationStep
|
||||
{
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
*/
|
||||
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options)
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
if (!$schema->hasTable('ncdownloader_info')) {
|
||||
$table = $schema->createTable('ncdownloader_info');
|
||||
$table->addColumn('id', 'integer', [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 10,
|
||||
]);
|
||||
$table->addColumn('uid', 'string', [
|
||||
'notnull' => false,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->addColumn('gid', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 32,
|
||||
]);
|
||||
$table->addColumn('filename', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('type', 'smallint', [
|
||||
'notnull' => true,
|
||||
'length' => 4,
|
||||
'default' => 1,
|
||||
'comment' => "Download Type(Aria2 = 1,Youtube = 2,Others = 3)",
|
||||
]);
|
||||
$table->addColumn('status', 'smallint', [
|
||||
'notnull' => true,
|
||||
'length' => 1,
|
||||
'default' => 1,
|
||||
]);
|
||||
$table->addColumn('followedby', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 16,
|
||||
'default' => 0,
|
||||
]);
|
||||
$table->addColumn('timestamp', 'bigint', [
|
||||
'notnull' => true,
|
||||
'length' => 15,
|
||||
'default' => 0,
|
||||
]);
|
||||
$table->addColumn('data', 'blob', [
|
||||
'notnull' => false,
|
||||
'default' => null,
|
||||
]);
|
||||
$table->setPrimaryKey(['id','gid']);
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
*/
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options)
|
||||
{
|
||||
}
|
||||
}
|
||||
56
lib/Search/Sites/TPB.php
Normal file
56
lib/Search/Sites/TPB.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Search\Sites;
|
||||
|
||||
//The Piratebay
|
||||
class TPB
|
||||
{
|
||||
//html content
|
||||
private $content = null;
|
||||
public $baseUrl = "https://piratebay.live/search/";
|
||||
|
||||
public function __construct($crawler, $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->crawler = $crawler;
|
||||
}
|
||||
public function search($keyword)
|
||||
{
|
||||
$this->searchUrl = $this->baseUrl . trim($keyword);
|
||||
$this->crawler->add($this->getContent());
|
||||
return $this->parse();
|
||||
}
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
public function getContent()
|
||||
{
|
||||
if ($this->content) {
|
||||
return $this->content;
|
||||
}
|
||||
$response = $this->client->request('GET', $this->searchUrl);
|
||||
return $response->getContent();
|
||||
}
|
||||
public function parse()
|
||||
{
|
||||
$data = $this->crawler->filter("#searchResult tr")->each(function ($node, $i) {
|
||||
|
||||
if ($node->getNode(0)) {
|
||||
try {
|
||||
$title = $node->filter("a.detLink")->text();
|
||||
$info = $node->filter("font.detDesc")->text();
|
||||
$numSeeders = $node->filter("td:nth-child(3)")->text();
|
||||
$magnetLink = $node->filter("td:nth-child(2) > a:nth-child(2)")->attr("href");
|
||||
$parts = explode(',', $info);
|
||||
$info = $parts[0] . "-" . $parts[1];
|
||||
return ['title' => $title, 'data-link' => $magnetLink, 'seeders' => $numSeeders, 'info' => $info];
|
||||
} catch (\Exception $e) {
|
||||
//echo $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
33
lib/Search/torrentSearch.php
Normal file
33
lib/Search/torrentSearch.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Search;
|
||||
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
use OCA\NcDownloader\Search\Sites\TPB;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
|
||||
class torrentSearch
|
||||
{
|
||||
|
||||
public static function go($keyword)
|
||||
{
|
||||
$client = HttpClient::create();
|
||||
$crawler = new Crawler();
|
||||
$tpb = new TPB($crawler, $client);
|
||||
$data = $tpb->search($keyword);
|
||||
self::addAction($data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
private static function addAction(&$data)
|
||||
{
|
||||
foreach ($data as $key => &$value) {
|
||||
if (!$value) {
|
||||
continue;
|
||||
}
|
||||
$value['actions'][] = array("name" => 'add', 'path' => '/index.php/apps/ncdownloader/new');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
62
lib/Settings/Admin.php
Normal file
62
lib/Settings/Admin.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Settings;
|
||||
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Settings\ISettings;
|
||||
use OCA\NcDownloader\Tools\Settings;
|
||||
|
||||
class Admin implements ISettings {
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $connection;
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
public function __construct(IDBConnection $connection,
|
||||
ITimeFactory $timeFactory,
|
||||
IConfig $config) {
|
||||
$this->connection = $connection;
|
||||
$this->timeFactory = $timeFactory;
|
||||
$this->config = $config;
|
||||
$this->UserId = \OC::$server->getUserSession()->getUser()->getUID();
|
||||
$this->settings = new Settings($this->UserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
$this->settings->setType($this->settings::SYSTEM);
|
||||
$parameters = [
|
||||
"path" => "/apps/ncdownloader/admin/save",
|
||||
"ncd_yt_binary" => $this->settings->get("ncd_yt_binary"),
|
||||
"ncd_aria2_binary" => $this->settings->get("ncd_aria2_binary"),
|
||||
"ncd_rpctoken" => $this->settings->get("ncd_rpctoken"),
|
||||
];
|
||||
return new TemplateResponse('ncdownloader', 'settings/Admin', $parameters, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the section ID, e.g. 'sharing'
|
||||
*/
|
||||
public function getSection(): string {
|
||||
return 'ncdownloader';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int whether the form should be rather on the top or bottom of
|
||||
* the admin section. The forms are arranged in ascending order of the
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
*
|
||||
* E.g.: 70
|
||||
*/
|
||||
public function getPriority(): int {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
39
lib/Settings/AdminSection.php
Normal file
39
lib/Settings/AdminSection.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\NcDownloader\Settings;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Settings\IIconSection;
|
||||
|
||||
class AdminSection implements IIconSection {
|
||||
|
||||
/** @var IL10N */
|
||||
private $l;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
public function __construct(IL10N $l, IURLGenerator $urlGenerator) {
|
||||
$this->l = $l;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function getIcon(): string {
|
||||
return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg');
|
||||
}
|
||||
|
||||
public function getID(): string {
|
||||
return 'ncdownloader';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->l->t('Downloader Settings');
|
||||
}
|
||||
|
||||
public function getPriority(): int {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
68
lib/Settings/Personal.php
Normal file
68
lib/Settings/Personal.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Settings;
|
||||
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Settings\ISettings;
|
||||
use OCA\NcDownloader\Tools\Settings;
|
||||
use OCA\NcDownloader\Tools\Helper;
|
||||
|
||||
class Personal implements ISettings {
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $connection;
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
public function __construct(IDBConnection $connection,
|
||||
ITimeFactory $timeFactory,
|
||||
IConfig $config) {
|
||||
$this->connection = $connection;
|
||||
$this->timeFactory = $timeFactory;
|
||||
$this->config = $config;
|
||||
$this->UserId = \OC::$server->getUserSession()->getUser()->getUID();
|
||||
$this->settings = new Settings($this->UserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
$parameters = [
|
||||
"ncd_downloader_dir" => $this->settings->get("ncd_downloader_dir"),
|
||||
"ncd_torrents_dir" => $this->settings->get("ncd_torrents_dir"),
|
||||
"ncd_seed_ratio" => $this->settings->get("ncd_seed_ratio"),
|
||||
'ncd_seed_time_unit' => $this->settings->get("ncd_seed_time_unit"),
|
||||
'ncd_seed_time' => $this->settings->get("ncd_seed_time"),
|
||||
"path" => '/apps/ncdownloader/personal/save',
|
||||
];
|
||||
|
||||
//\OC_Util::addScript($this->appName, 'common');
|
||||
//\OC_Util::addScript($this->appName, 'settings/personal');
|
||||
//file_put_contents("/tmp/re.log",print_r($parameters,true));
|
||||
return new TemplateResponse('ncdownloader', 'settings/Personal', $parameters, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the section ID, e.g. 'sharing'
|
||||
*/
|
||||
public function getSection(): string {
|
||||
return 'ncdownloader';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int whether the form should be rather on the top or bottom of
|
||||
* the admin section. The forms are arranged in ascending order of the
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
*
|
||||
* E.g.: 70
|
||||
*/
|
||||
public function getPriority(): int {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
39
lib/Settings/PersonalSection.php
Normal file
39
lib/Settings/PersonalSection.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\NcDownloader\Settings;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Settings\IIconSection;
|
||||
|
||||
class PersonalSection implements IIconSection {
|
||||
|
||||
/** @var IL10N */
|
||||
private $l;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
public function __construct(IL10N $l, IURLGenerator $urlGenerator) {
|
||||
$this->l = $l;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function getIcon(): string {
|
||||
return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg');
|
||||
}
|
||||
|
||||
public function getID(): string {
|
||||
return 'ncdownloader';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->l->t('Downloader Settings');
|
||||
}
|
||||
|
||||
public function getPriority(): int {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
373
lib/Tools/Aria2.php
Normal file
373
lib/Tools/Aria2.php
Normal file
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
//use Symfony\Component\Process\ExecutableFinder;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class Aria2
|
||||
{
|
||||
//extra Aria2 download options
|
||||
private $options = array();
|
||||
//optional token for authenticating with Aria2
|
||||
private $token = null;
|
||||
//the aria2 method being invoked
|
||||
private $method = null;
|
||||
//the aria2c binary path
|
||||
private $bin = null;
|
||||
// return the following items when getting downloads info by default
|
||||
private $dataFilter = array(
|
||||
'status', 'followedBy', 'totalLength', 'errorMessage', 'dir', 'uploadLength', 'completedLength', 'downloadSpeed', 'files', 'numSeeders', 'connections', 'gid', 'following', 'bittorrent',
|
||||
);
|
||||
//whether to filter the response returned by aria2
|
||||
private $filterResponse = true;
|
||||
//absolute download path
|
||||
private $downloadDir;
|
||||
//the path to where hook scripts is stored
|
||||
public function __construct($options = array())
|
||||
{
|
||||
$options += array(
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6800,
|
||||
'dir' => '/tmp/Downloads',
|
||||
'token' => null,
|
||||
'conf_dir' => '/tmp/aria2',
|
||||
'settings' => [],
|
||||
);
|
||||
//turn keys in $options into variables
|
||||
extract($options);
|
||||
$this->setDownloadDir($dir);
|
||||
if (!empty($settings)) {
|
||||
foreach ($settings as $key => $value) {
|
||||
$this->setOption($key, $value);
|
||||
}
|
||||
}
|
||||
$this->bin = Helper::findBinaryPath('aria2c');
|
||||
$this->rpcUrl = sprintf("http://%s:%s/jsonrpc", $host, $port);
|
||||
$this->tokenString = $token ?? 'ncdownloader123';
|
||||
$this->setToken($this->tokenString);
|
||||
$this->confDir = $conf_dir;
|
||||
$this->sessionFile = $this->confDir . "/aria2.session";
|
||||
$this->confFile = $this->confDir . "/aria2.conf";
|
||||
$this->logFile = $this->confDir . "/aria2.log";
|
||||
}
|
||||
public function init()
|
||||
{
|
||||
$this->ch = curl_init($this->rpcUrl);
|
||||
curl_setopt_array($this->ch, array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
));
|
||||
$this->configure();
|
||||
}
|
||||
|
||||
public function setonDownloadStart($path)
|
||||
{
|
||||
$this->onDownloadStart = $path;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
|
||||
private function hasOption($key)
|
||||
{
|
||||
return (bool) isset($this->options[$key]);
|
||||
}
|
||||
|
||||
private function configure()
|
||||
{
|
||||
if (!is_dir($this->confDir)) {
|
||||
mkdir($this->confDir, 0755, true);
|
||||
}
|
||||
if (!file_exists($this->confDir . "/aria2.conf")) {
|
||||
file_put_contents($this->confDir . "/aria2.conf", $this->confTemplate());
|
||||
}
|
||||
if (!is_dir($dir = $this->getDownloadDir())) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
$this->followTorrent(true);
|
||||
}
|
||||
public function setToken($token)
|
||||
{
|
||||
$this->token = "token:$token";
|
||||
return $this;
|
||||
}
|
||||
public function setOption($key, $value)
|
||||
{
|
||||
$this->options[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
public function setDownloadDir($dir)
|
||||
{
|
||||
$this->setOption('dir', $dir);
|
||||
$this->downloadDir = $dir;
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function getDownloadDir()
|
||||
{
|
||||
return $this->downloadDir;
|
||||
}
|
||||
public function setFileName($file)
|
||||
{
|
||||
$this->options['out'] = $file;
|
||||
return $this;
|
||||
}
|
||||
public function followTorrent($follow)
|
||||
{
|
||||
$this->options['follow-torrent'] = $follow;
|
||||
return $this;
|
||||
}
|
||||
private function request($data)
|
||||
{
|
||||
$this->init();
|
||||
$defaults = array(
|
||||
'jsonrpc' => '2.0',
|
||||
'id' => 'ncdownloader',
|
||||
'method' => 'aria2.addUri',
|
||||
'params' => null,
|
||||
);
|
||||
|
||||
$data += $defaults;
|
||||
$this->content = json_encode($data);
|
||||
|
||||
if (isset($this->content)) {
|
||||
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->content);
|
||||
}
|
||||
$resp = curl_exec($this->ch);
|
||||
curl_close($this->ch);
|
||||
return json_decode($resp, 1);
|
||||
}
|
||||
public function getFollowingGid($gid)
|
||||
{
|
||||
$data = $this->tellStatus($gid);
|
||||
if (!is_array($data)) {
|
||||
return 0;
|
||||
}
|
||||
$data = reset($data);
|
||||
return ($data['following'] ?? 0);
|
||||
}
|
||||
public function tellFail($range = [0, 999])
|
||||
{
|
||||
$this->filterResponse = false;
|
||||
$resp = $this->tellStopped($range);
|
||||
$result = $this->sortDownloadsResult($resp['result'], ['complete', 'removed']);
|
||||
$this->filterResponse = true;;
|
||||
return $result;
|
||||
}
|
||||
public function getCounters()
|
||||
{
|
||||
$active = is_array($data = $this->tellActive([])) ? count($data) : 0;
|
||||
$waiting = is_array($data = $this->tellWaiting([0, 999])) ? count($data) : 0;
|
||||
$stopped = is_array($data = $this->tellStopped([0, 999])) ? count($data) : 0;
|
||||
$fail = is_array($data = $this->tellFail([0, 999])) ? count($data) : 0;
|
||||
return ['active' => $active, 'waiting' => $waiting, 'complete' => $stopped, 'fail' => $fail];
|
||||
}
|
||||
public function tellAll()
|
||||
{
|
||||
$this->filterResponse = false;
|
||||
return array_merge($this->tellActive([]), $this->tellWaiting([0, 999]), $this->tellStopped([0, 999]));
|
||||
}
|
||||
public function __call($name, $args)
|
||||
{
|
||||
$this->methodName = $name;
|
||||
$data = array();
|
||||
if (isset($args[0]) && is_array($args[0]) && count($args) == 1 && strtolower($name) !== "adduri") {
|
||||
$args = reset($args);
|
||||
}
|
||||
switch ($name) {
|
||||
case "addUri":
|
||||
array_push($args, $this->options);
|
||||
break;
|
||||
case "tellActive":
|
||||
case "tellWaiting":
|
||||
case "tellStopped":
|
||||
array_push($args, $this->dataFilter);
|
||||
break;
|
||||
case "tellStatus":
|
||||
case "getFiles":
|
||||
array_push($args, $this->dataFilter);
|
||||
break;
|
||||
}
|
||||
if (isset($this->token)) {
|
||||
array_unshift($args, $this->token);
|
||||
}
|
||||
$data = array('params' => $args, 'method' => 'aria2.' . $name);
|
||||
$rawResp = $this->request($data);
|
||||
|
||||
if (!$this->filterResponse) {
|
||||
return $rawResp;
|
||||
}
|
||||
return $this->parseResp($rawResp);
|
||||
}
|
||||
private function sortDownloadsResult($result, $statusFilter = null)
|
||||
{
|
||||
$data = [];
|
||||
if (!isset($statusFilter)) {
|
||||
$statusFilter = ['error'];
|
||||
}
|
||||
if (empty($result)) {
|
||||
return [];
|
||||
}
|
||||
foreach ($result as $info) {
|
||||
$info = Helper::filterData($info);
|
||||
if (isset($info['files'])) {
|
||||
foreach ($info['files'] as $key => &$files) {
|
||||
$files = Helper::filterData($files, array('path', 'length'));
|
||||
}
|
||||
}
|
||||
if (in_array($info['status'], $statusFilter)) {
|
||||
continue;
|
||||
}
|
||||
array_push($data, $info);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
public function parseResp($resp = array())
|
||||
{
|
||||
$data = array();
|
||||
if (isset($resp['error']) && isset($resp['error']['message'])) {
|
||||
$data['error'] = $resp['error']['message'];
|
||||
return $data;
|
||||
}
|
||||
$result = $resp['result'] ?? null;
|
||||
if (!isset($result)) {
|
||||
return $data;
|
||||
}
|
||||
if ($this->methodName === 'tellStatus' && isset($result['files'])) {
|
||||
foreach ($result['files'] as $key => &$files) {
|
||||
$files = Helper::filterData($files, array('path', 'length'));
|
||||
}
|
||||
array_push($data, $result);
|
||||
return $data;
|
||||
}
|
||||
// parse response for tellActive,tellWaiting,and tellStopped
|
||||
if (strpos($this->methodName, "tell") !== false && is_array($result)) {
|
||||
return $this->sortDownloadsResult($result);
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
public function getStatus($gid)
|
||||
{
|
||||
return $this->tellStatus($gid);
|
||||
}
|
||||
public function restart()
|
||||
{
|
||||
$this->stop();
|
||||
$this->start();
|
||||
}
|
||||
|
||||
public function getDefaults()
|
||||
{
|
||||
return [
|
||||
'--continue',
|
||||
'--daemon=true',
|
||||
'--enable-rpc=true',
|
||||
'--rpc-secret=' . $this->tokenString,
|
||||
'--listen-port=51413',
|
||||
'--save-session=' . $this->sessionFile,
|
||||
'--input-file=' . $this->sessionFile,
|
||||
'--log=' . $this->logFile,
|
||||
'--rpc-listen-port=6800',
|
||||
'--follow-torrent=true',
|
||||
'--enable-dht=true',
|
||||
'--enable-peer-exchange=true',
|
||||
'--peer-id-prefix=-TR2770-',
|
||||
'--user-agent=Transmission/2.77',
|
||||
'--log-level=info',
|
||||
'--seed-ratio=1.0',
|
||||
'--bt-seed-unverified=true',
|
||||
'--max-overall-upload-limit=1M',
|
||||
'--max-overall-download-limit=0',
|
||||
'--max-connection-per-server=4',
|
||||
'--max-concurrent-downloads=5',
|
||||
];
|
||||
}
|
||||
public function start($bin = null)
|
||||
{
|
||||
//aria2c wont't start without any errors when input-file is set but missing
|
||||
if (!file_exists($this->sessionFile)) {
|
||||
file_put_contents($this->sessionFile, '');
|
||||
}
|
||||
|
||||
//$process = new Process([$this->bin, "--conf-path=" . $this->confFile]);
|
||||
$defaults = $this->getDefaults();
|
||||
array_unshift($defaults, $this->bin);
|
||||
$process = new Process($defaults);
|
||||
try {
|
||||
$process->mustRun();
|
||||
$output = $process->getOutput();
|
||||
} catch (ProcessFailedException $exception) {
|
||||
$error = $exception->getMessage();
|
||||
}
|
||||
$resp = [];
|
||||
if (isset($error)) {
|
||||
$resp['error'] = $error;
|
||||
$resp['status'] = false;
|
||||
} else {
|
||||
$resp['status'] = true;
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
public function isInstalled()
|
||||
{
|
||||
return (bool) isset($this->bin);
|
||||
}
|
||||
public function isRunning()
|
||||
{
|
||||
$resp = $this->getSessionInfo();
|
||||
return (bool) $resp;
|
||||
}
|
||||
public function stop()
|
||||
{
|
||||
$resp = $this->shutdown();
|
||||
return $resp ?? null;
|
||||
}
|
||||
private function confTemplate()
|
||||
{
|
||||
return <<<EOF
|
||||
continue
|
||||
daemon=true
|
||||
#dir=/home/aria2/Downloads
|
||||
#file-allocation=falloc
|
||||
log-level=info
|
||||
max-connection-per-server=4
|
||||
max-concurrent-downloads=5
|
||||
max-overall-download-limit=0
|
||||
min-split-size=5M
|
||||
enable-http-pipelining=true
|
||||
#interface=127.0.0.1
|
||||
enable-rpc=true
|
||||
rpc-secret=$this->tokenString
|
||||
rpc-listen-all=true
|
||||
rpc-listen-port=6800
|
||||
follow-torrent=true
|
||||
listen-port=51413
|
||||
enable-dht=true
|
||||
enable-peer-exchange=true
|
||||
peer-id-prefix=-TR2770-
|
||||
user-agent=Transmission/2.77
|
||||
seed-ratio=0.1
|
||||
bt-seed-unverified=true
|
||||
max-overall-upload-limit=1M
|
||||
#on-download-complete=$this->onDownloadComplete
|
||||
#on-download-error=$this->onDownloadError
|
||||
#on-download-start=$this->onDownloadStart
|
||||
save-session=$this->sessionFile
|
||||
input-file=$this->sessionFile
|
||||
log=$this->logFile
|
||||
EOF;
|
||||
}
|
||||
}
|
||||
90
lib/Tools/DBConn.php
Normal file
90
lib/Tools/DBConn.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
class DBConn
|
||||
{
|
||||
//@var OC\DB\ConnectionAdapter
|
||||
private $conn;
|
||||
private $table = "ncdownloader_info";
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->conn = \OC::$server->getDatabaseConnection();
|
||||
$this->queryBuilder = $this->conn->getQueryBuilder();
|
||||
//$container = \OC::$server->query(\OCP\IServerContainer::class);
|
||||
//Helper::debug(get_class($container->query(\OCP\RichObjectStrings\IValidator::class)));
|
||||
//$this->conn = \OC::$server->query(Connection::class);//working only with 22
|
||||
//$this->connAdapter = \OC::$server->getDatabaseConnection();
|
||||
//$this->conn = $this->connAdapter->getInner();
|
||||
}
|
||||
|
||||
public function create($insert)
|
||||
{
|
||||
$inserted = (bool) $this->conn->insertIfNotExist('*PREFIX*' . $this->table, $insert, [
|
||||
'gid',
|
||||
]);
|
||||
return $inserted;
|
||||
}
|
||||
public function getAll()
|
||||
{
|
||||
//OC\DB\QueryBuilder\QueryBuilder
|
||||
$queryBuilder = $this->queryBuilder
|
||||
->select('filename', 'type', 'gid', 'timestamp', 'status')
|
||||
->from($this->table)
|
||||
->execute();
|
||||
return $queryBuilder->fetchAll();
|
||||
}
|
||||
|
||||
public function getByUid($uid)
|
||||
{
|
||||
$queryBuilder = $this->queryBuilder
|
||||
->select('*')
|
||||
->from($this->table)
|
||||
->where('uid = :uid')
|
||||
->setParameter('uid', $uid)
|
||||
->execute();
|
||||
return $queryBuilder->fetchAll();
|
||||
}
|
||||
|
||||
public function getByGid($gid)
|
||||
{
|
||||
$queryBuilder = $this->queryBuilder
|
||||
->select('*')
|
||||
->from($this->table)
|
||||
->where('gid = :gid')
|
||||
->setParameter('gid', $gid)
|
||||
->execute();
|
||||
return $queryBuilder->fetch();
|
||||
}
|
||||
|
||||
public function save(array $keys, $values = array())
|
||||
{
|
||||
return $this->conn->setValues($this->table, $keys, $values);
|
||||
}
|
||||
|
||||
public function deleteByGid($gid)
|
||||
{
|
||||
// $sql = sprintf("DELETE FROM %s WHERE gid = ?",'*PREFIX*'.$this->table);
|
||||
// return $this->conn->executeStatement($sql,array($gid));
|
||||
$qb = $this->queryBuilder
|
||||
->delete($this->table)
|
||||
->where('gid = :gid')
|
||||
->setParameter('gid', $gid);
|
||||
return $qb->execute();
|
||||
}
|
||||
public function execute($sql, $values)
|
||||
{
|
||||
return $this->conn->executeStatement($sql, $values);
|
||||
|
||||
// for some reason this doesn't work
|
||||
$query = $this->queryBuilder;
|
||||
$query->update('ncdownloader_info')
|
||||
->set("data", $query->createNamedParameter($value))
|
||||
->where($query->expr()->eq('gid', $query->createNamedParameter($gid)));
|
||||
// ->setParameter('gid', $gid);
|
||||
// return $query->execute();
|
||||
//return $query->getSQL();
|
||||
return $this->queryBuilder->getSQL();
|
||||
}
|
||||
|
||||
}
|
||||
86
lib/Tools/ExecutableFinder.php
Normal file
86
lib/Tools/ExecutableFinder.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
/**
|
||||
* Generic executable finder.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ExecutableFinder
|
||||
{
|
||||
private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
|
||||
|
||||
/**
|
||||
* Replaces default suffixes of executable.
|
||||
*/
|
||||
public function setSuffixes(array $suffixes)
|
||||
{
|
||||
$this->suffixes = $suffixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new possible suffix to check for executable.
|
||||
*/
|
||||
public function addSuffix(string $suffix)
|
||||
{
|
||||
$this->suffixes[] = $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an executable by name.
|
||||
*
|
||||
* @param string $name The executable name (without the extension)
|
||||
* @param string|null $default The default to return if no executable is found
|
||||
* @param array $extraDirs Additional dirs to check into
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function find(string $name, string $default = null, array $extraDirs = [])
|
||||
{
|
||||
if (ini_get('open_basedir')) {
|
||||
$searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
|
||||
$dirs = [];
|
||||
foreach ($searchPath as $path) {
|
||||
// Silencing against https://bugs.php.net/69240
|
||||
if (@is_dir($path)) {
|
||||
$dirs[] = $path;
|
||||
} else {
|
||||
if (basename($path) == $name && @is_executable($path)) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$dirs = array_merge(
|
||||
explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
|
||||
$extraDirs
|
||||
);
|
||||
}
|
||||
|
||||
$suffixes = [''];
|
||||
if ('\\' === \DIRECTORY_SEPARATOR) {
|
||||
$pathExt = getenv('PATHEXT');
|
||||
$suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
|
||||
}
|
||||
foreach ($suffixes as $suffix) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
28
lib/Tools/File.php
Normal file
28
lib/Tools/File.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
use OCA\NcDownloader\Tools\Helper;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\Utils\Scanner;
|
||||
use \OCP\EventDispatcher\IEventDispatcher;
|
||||
|
||||
class File
|
||||
{
|
||||
public static function syncFolder($dir)
|
||||
{
|
||||
$user = \OC::$server->getUserSession()->getUser()->getUID();
|
||||
$logger = \OC::$server->getLogger();
|
||||
$scanner = new Scanner($user, \OC::$server->getDatabaseConnection(), \OC::$server->query(IEventDispatcher::class), $logger);
|
||||
$path = Filesystem::getRoot() . "/" . ltrim($dir, '/\\');
|
||||
try {
|
||||
$scanner->scan($path);
|
||||
// Helper::debug($logger->getLogPath());
|
||||
//$logger->warning($logger->getLogPath(),['app' =>'Ncdownloader']);
|
||||
} catch (ForbiddenException $e) {
|
||||
$logger->warning("Make sure you're running the scan command only as the user the web server runs as");
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$logger->warning("Exception during scan: " . $e->getMessage() . $e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
257
lib/Tools/Helper.php
Normal file
257
lib/Tools/Helper.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
use OCA\NcDownloader\Tools\aria2Options;
|
||||
|
||||
class Helper
|
||||
{
|
||||
public static function isUrl($URL)
|
||||
{
|
||||
$URLPattern = '%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}'
|
||||
. ']+-?)*[a-z\d\x{00a1}-\x{ffff}]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.'
|
||||
. '[a-z\x{00a1}-\x{ffff}]{2,6}))(?::\d+)?(?:[^\s]*)?$%iu';
|
||||
|
||||
preg_match($URLPattern, $URL, $Matches);
|
||||
if (count($Matches) === 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isMagnet($url)
|
||||
{
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||
return strtolower($scheme) == "magnet";
|
||||
}
|
||||
public static function isHttp($url)
|
||||
{
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||
return (in_array($scheme, array('http', 'https')));
|
||||
}
|
||||
public static function isFtp($url)
|
||||
{
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||
return strtolower($scheme) == "ftp";
|
||||
}
|
||||
public static function isGetUrlSite($url)
|
||||
{
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$sites = ['twitter.com', 'youtube.com'];
|
||||
return (bool) (in_array($host, $sites));
|
||||
}
|
||||
public static function parseUrl($url)
|
||||
{
|
||||
parse_str(str_replace('tr=', 'tr[]=', parse_url($url, PHP_URL_QUERY)), $query);
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function getUrlPath($url)
|
||||
{
|
||||
$path = parse_url($url, PHP_URL_PATH);
|
||||
return self::cleanString(basename($path));
|
||||
}
|
||||
public static function getFilename($url)
|
||||
{
|
||||
if (self::isMagnet($url)) {
|
||||
return self::parseUrl($url)['dn'];
|
||||
} else {
|
||||
return self::getUrlPath($url);
|
||||
}
|
||||
}
|
||||
public static function formatBytes($size, $precision = 2)
|
||||
{
|
||||
if ($size < 1) {
|
||||
return '0';
|
||||
}
|
||||
$base = log($size, 1024);
|
||||
$suffixes = array('', 'K', 'M', 'G', 'T');
|
||||
return round(pow(1024, $base - floor($base)), $precision) . ' ' . $suffixes[floor($base)];
|
||||
}
|
||||
|
||||
public static function checkMediaType($url, $type = 'video/mp4')
|
||||
{
|
||||
$result = parse_url($url);
|
||||
if (isset($result['scheme']) && self::isHttp($url)) {
|
||||
if (isset($result['query'])) {
|
||||
parse_str($result['query'], $output);
|
||||
if (!isset($output['mime'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (bool) ($output['mime'] == trim($type));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isYoutubeType($url)
|
||||
{
|
||||
$regex = '%^(?:(?:https?)://)(?:[a-z0-9_]*\.)?(?:twitter|youtube)\.com/%i';
|
||||
return (bool) preg_match($regex, $url);
|
||||
}
|
||||
|
||||
public static function cleanString($string)
|
||||
{
|
||||
$replace = array
|
||||
(
|
||||
'/[áàâãªä]/u' => 'a',
|
||||
'/[ÁÀÂÃÄ]/u' => 'A',
|
||||
'/[ÍÌÎÏ]/u' => 'I',
|
||||
'/[íìîï]/u' => 'i',
|
||||
'/[éèêë]/u' => 'e',
|
||||
'/[ÉÈÊË]/u' => 'E',
|
||||
'/[óòôõºö]/u' => 'o',
|
||||
'/[ÓÒÔÕÖ]/u' => 'O',
|
||||
'/[úùûü]/u' => 'u',
|
||||
'/[ÚÙÛÜ]/u' => 'U',
|
||||
'/ç/' => 'c',
|
||||
'/Ç/' => 'C',
|
||||
'/ñ/' => 'n',
|
||||
'/Ñ/' => 'N',
|
||||
'/–/' => '-', // UTF-8 hyphen to "normal" hyphen
|
||||
'/[’‘‹›‚]/u' => '', // Literally a single quote
|
||||
'/[“”«»„]/u' => '', // Double quote
|
||||
'/ /' => '_', // nonbreaking space(equiv. to 0x160)
|
||||
'/[^a-z0-9_\s.-]/i' => '_',
|
||||
);
|
||||
return preg_replace(array_keys($replace), array_values($replace), $string);
|
||||
}
|
||||
|
||||
public static function debug($msg)
|
||||
{
|
||||
$logger = \OC::$server->getLogger();
|
||||
$logger->debug($msg, ['app' => 'ncdownloader']);
|
||||
}
|
||||
|
||||
public static function log($msg, $file = "/tmp/nc.log")
|
||||
{
|
||||
file_put_contents($file, print_r($msg, true));
|
||||
}
|
||||
public static function filterData($data, $filter = null)
|
||||
{
|
||||
if (!isset($filter)) {
|
||||
$filter = array(
|
||||
'status', 'followedBy', 'totalLength', 'errorMessage', 'dir', 'uploadLength', 'completedLength', 'downloadSpeed', 'files', 'numSeeders', 'connections', 'gid', 'following',
|
||||
);
|
||||
}
|
||||
$value = array_filter($data, function ($k) use ($filter) {
|
||||
return (in_array($k, $filter));
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getFolderName($folder, $prefix)
|
||||
{
|
||||
$folder = ltrim($folder, $prefix);
|
||||
return substr($folder, 0, strpos($folder, '/'));
|
||||
}
|
||||
|
||||
public static function Download($url, $file = null)
|
||||
{
|
||||
if (!isset($file)) {
|
||||
$file = "/tmp/" . self::getFilename($url);
|
||||
}
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||
$result = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
file_put_contents($file, $result);
|
||||
}
|
||||
|
||||
public static function is_function_enabled($function_name)
|
||||
{
|
||||
if (!function_exists($function_name)) {
|
||||
return false;
|
||||
}
|
||||
$ini = \OC::$server->getIniWrapper();
|
||||
$disabled = explode(',', $ini->get('disable_functions') ?: '');
|
||||
$disabled = array_map('trim', $disabled);
|
||||
if (in_array($function_name, $disabled)) {
|
||||
return false;
|
||||
}
|
||||
$disabled = explode(',', $ini->get('suhosin.executor.func.blacklist') ?: '');
|
||||
$disabled = array_map('trim', $disabled);
|
||||
if (in_array($function_name, $disabled)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function findBinaryPath($program)
|
||||
{
|
||||
$memcache = \OC::$server->getMemCacheFactory()->createDistributed('findBinaryPath');
|
||||
if ($memcache->hasKey($program)) {
|
||||
return $memcache->get($program);
|
||||
}
|
||||
$dataPath = \OC::$server->getSystemConfig()->getValue('datadirectory');
|
||||
$paths = ['/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/opt/bin', $dataPath . "/bin"];
|
||||
$result = null;
|
||||
if (self::is_function_enabled('exec')) {
|
||||
$exeSniffer = new ExecutableFinder();
|
||||
// Returns null if nothing is found
|
||||
$result = $exeSniffer->find($program, null, $paths);
|
||||
}
|
||||
// store the value for 5 minutes
|
||||
$memcache->set($program, $result, 300);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function formatInterval($interval, $granularity = 2)
|
||||
{
|
||||
$units = array(
|
||||
'1 year|years' => 31536000,
|
||||
'1 monthmonths' => 2592000,
|
||||
'1 week|weeks' => 604800,
|
||||
'1 day|days' => 86400,
|
||||
'1 hour|hours' => 3600,
|
||||
'1 min|mins' => 60,
|
||||
'1 sec|sec' => 1,
|
||||
);
|
||||
$output = '';
|
||||
foreach ($units as $key => $value) {
|
||||
$key = explode('|', $key);
|
||||
if ($interval >= $value) {
|
||||
$output .= ($output ? ' ' : '') . self::formatPlural(floor($interval / $value), $key[0], $key[1]);
|
||||
$interval %= $value;
|
||||
$granularity--;
|
||||
}
|
||||
if ($granularity == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $output ? $output : '0 sec';
|
||||
}
|
||||
|
||||
public static function formatPlural($count, $singular, $plural)
|
||||
{
|
||||
if ($count == 1) {
|
||||
return $singular;
|
||||
} else {
|
||||
return $count . " " . $plural;
|
||||
}
|
||||
}
|
||||
public static function aria2Options()
|
||||
{
|
||||
return aria2Options::get();
|
||||
}
|
||||
|
||||
public static function getTableTitles($type = null)
|
||||
{
|
||||
$general = ['filename', 'status', 'actions'];
|
||||
if(!isset($type)){
|
||||
return $general;
|
||||
}
|
||||
$titles = [
|
||||
'active' => ['filename', 'speed', 'progress', 'actions'],
|
||||
'waiting' => $general,
|
||||
'fail' => $general,
|
||||
'complete' => $general,
|
||||
];
|
||||
return $titles[$type];
|
||||
}
|
||||
|
||||
}
|
||||
235
lib/Tools/Settings.php
Normal file
235
lib/Tools/Settings.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
use OC\AllConfig;
|
||||
|
||||
class Settings extends AllConfig
|
||||
{
|
||||
//@config OC\AppConfig
|
||||
private $config;
|
||||
|
||||
//@OC\SystemConfig
|
||||
private $sysConfig;
|
||||
|
||||
//@OC\AllConfig
|
||||
private $allConfig;
|
||||
|
||||
//type of settings (system = 1 or app =2)
|
||||
private $type;
|
||||
const SYSTEM = 0x001;
|
||||
const USER = 0x010;
|
||||
const APP = 0x100;
|
||||
public function __construct($user = null)
|
||||
{
|
||||
$this->appConfig = \OC::$server->getAppConfig();
|
||||
$this->sysConfig = \OC::$server->getSystemConfig();
|
||||
$this->appName = 'ncdownloader';
|
||||
$this->type = self::USER;
|
||||
$this->user = $user;
|
||||
$this->allConfig = new AllConfig($this->sysConfig);
|
||||
//$this->connAdapter = \OC::$server->getDatabaseConnection();
|
||||
//$this->conn = $this->connAdapter->getInner();
|
||||
}
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
if ($this->type == self::USER && isset($this->user)) {
|
||||
return $this->allConfig->getUserValue($this->user, $this->appName, $key, $default);
|
||||
} else if ($this->type == self::SYSTEM) {
|
||||
return $this->allConfig->getSystemValue($key, $default);
|
||||
} else {
|
||||
return $this->allConfig->getAppValue($this->appName, $key, $default);
|
||||
}
|
||||
}
|
||||
public function getAria2()
|
||||
{
|
||||
$settings = $this->allConfig->getUserValue($this->user, $this->appName, "custom_aria2_settings", '');
|
||||
return json_decode($settings, 1);
|
||||
}
|
||||
public function getAll()
|
||||
{
|
||||
if ($this->type === self::APP) {
|
||||
return $this->getAllAppValues();
|
||||
} else {
|
||||
$data = $this->getAllUserSettings();
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
public function save($key, $value)
|
||||
{
|
||||
if ($this->type == self::USER && isset($this->user)) {
|
||||
return $this->allConfig->setUserValue($this->user, $this->appName, $key, $value);
|
||||
} else if ($this->type == self::SYSTEM) {
|
||||
return $this->allConfig->setSystemValue($key, $value);
|
||||
} else {
|
||||
return $this->allConfig->setAppValue($this->appName, $key, $value);
|
||||
}
|
||||
|
||||
}
|
||||
public function getAllAppValues()
|
||||
{
|
||||
$keys = $this->getAllKeys();
|
||||
$value = [];
|
||||
foreach ($keys as $key) {
|
||||
$value[$key] = $this->allConfig->getAppValue($this->appName, $key);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
public function getAllKeys()
|
||||
{
|
||||
return $this->allConfig->getAppKeys($this->appName);
|
||||
}
|
||||
|
||||
public function getAllUserSettings()
|
||||
{
|
||||
$keys = $this->allConfig->getUserKeys($this->user, $this->appName);
|
||||
$value = [];
|
||||
foreach ($keys as $key) {
|
||||
$value[$key] = $this->allConfig->getUserValue($this->user, $this->appName, $key);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class customSettings
|
||||
{
|
||||
private $name = null;
|
||||
private $dbType = 0;
|
||||
private $table = 'ncdownloader_settings';
|
||||
private $uid = null;
|
||||
const PGSQL = 1, MYSQL = 2, SQL = 3;
|
||||
/* @var OC\DB\ConnectionAdapter */
|
||||
private $connAdapter;
|
||||
/* @var OC\DB\Connection */
|
||||
private $conn;
|
||||
private $type = 2; //personal = 2,admin =1
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (\OC::$server->getConfig()->getSystemValue('dbtype') == 'pgsql') {
|
||||
$this->dbType = PGSQL;
|
||||
}
|
||||
$this->connAdapter = \OC::$server->getDatabaseConnection();
|
||||
$this->conn = $this->connAdapter->getInner();
|
||||
|
||||
$this->prefixTable();
|
||||
}
|
||||
|
||||
private function prefixTable()
|
||||
{
|
||||
$this->table = '*PREFIX*' . $this->table;
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
public function set($name, $value)
|
||||
{
|
||||
if ($this->have($name)) {
|
||||
$this->update($name, $value);
|
||||
} else {
|
||||
$this->insert($name, $value);
|
||||
}
|
||||
}
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
|
||||
if (isset($this->uid)) {
|
||||
$sql = sprintf("SELECT value FROM %s WHERE uid = ? AND name = ? LIMIT 1", $this->table);
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$result = $query->execute(array($this->uid, $name));
|
||||
} else {
|
||||
$sql = sprintf("SELECT value FROM %s WHERE name = ? LIMIT 1", $this->table);
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$result = $query->execute(array($name));
|
||||
}
|
||||
if ($query->rowCount() == 1) {
|
||||
return $result->fetchOne();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setUID($uid)
|
||||
{
|
||||
$this->uid = $uid;
|
||||
}
|
||||
|
||||
public function setTable($table)
|
||||
{
|
||||
$this->table = $table;
|
||||
}
|
||||
public function getTable()
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
public function have($name)
|
||||
{
|
||||
if (isset($this->uid)) {
|
||||
$sql = sprintf("SELECT value FROM %s WHERE uid = ? AND name = ? AND type = ? LIMIT 1", $this->table);
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$query->execute(array($name, $this->uid, $this->type));
|
||||
} else {
|
||||
$sql = sprintf("SELECT value FROM %s WHERE name = ? AND type = ? LIMIT 1", $this->table);
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$query->execute(array($name, $this->type));
|
||||
}
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
{
|
||||
$sql = 'SELECT `name`, `value` FROM `*PREFIX*' . $this->table . '`'
|
||||
. (!is_null($this->uid) ? ' WHERE `UID` = ?' : '');
|
||||
if ($this->DbType == 1) {
|
||||
$sql = 'SELECT "name", "value" FROM *PREFIX*' . $this->table . ''
|
||||
. (!is_null($this->uid) ? ' WHERE "uid" = ?' : '');
|
||||
}
|
||||
$query = \OC_DB::prepare($sql);
|
||||
|
||||
if (!is_null($this->uid)) {
|
||||
return $query->execute(array($this->uid));
|
||||
} else {
|
||||
return $query->execute();
|
||||
}
|
||||
}
|
||||
|
||||
public function update($value)
|
||||
{
|
||||
if (isset($this->uid)) {
|
||||
$sql = sprintf("UPDATE %s SET value = ? WHERE name = ? AND type = ? AND uid = ?", $this->table);
|
||||
//OCP\DB\IPreparedStatement
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$query->execute(array($value, $name, $this->type, $this->uid));
|
||||
} else {
|
||||
$sql = sprintf("UPDATE %s SET value = ? WHERE name = ? AND type = ?", $this->table);
|
||||
//OCP\DB\IPreparedStatement
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$query->execute(array($value, $name, $this->type));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function insert($name, $value)
|
||||
{
|
||||
$sql = sprintf("INSERT INTO %s (name,value,type,uid) VALUES(?,?,?,?)", $this->table);
|
||||
//OCP\DB\IPreparedStatement
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$query->execute(array($name, $value, $this->type, $this->uid));
|
||||
|
||||
}
|
||||
}
|
||||
127
lib/Tools/YouTube.php
Normal file
127
lib/Tools/YouTube.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
use OCA\NcDownloader\Tools\Helper;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class YouTube
|
||||
{
|
||||
private $ipv4Only;
|
||||
private $audioOnly = 0;
|
||||
private $audioFormat, $videoFormat = 'mp4';
|
||||
private $options = [];
|
||||
public function __construct()
|
||||
{
|
||||
$this->bin = Helper::findBinaryPath('youtube-dl');
|
||||
}
|
||||
|
||||
public function GetUrlOnly()
|
||||
{
|
||||
$this->addOption('--get-filename');
|
||||
$this->addOption('--get-url');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function create()
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function setDownloadDir($dir)
|
||||
{
|
||||
$this->downloadDir = rtrim($dir, '/');
|
||||
}
|
||||
|
||||
public function prependOption($option)
|
||||
{
|
||||
array_unshift($this->options, $option);
|
||||
}
|
||||
|
||||
public function download($url)
|
||||
{
|
||||
$this->downloadDir = $this->downloadDir ?? "/tmp/downloads";
|
||||
$this->prependOption($this->downloadDir . "/%(id)s-%(title)s.%(ext)s");
|
||||
$this->prependOption("-o");
|
||||
$this->setUrl($url);
|
||||
$this->prependOption($this->bin);
|
||||
// $this->buildCMD();
|
||||
$process = new Process($this->options);
|
||||
try {
|
||||
$process->mustRun();
|
||||
$output = $process->getOutput();
|
||||
} catch (ProcessFailedException $exception) {
|
||||
$output = $exception->getMessage();
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function getDownloadUrl($url)
|
||||
{
|
||||
$this->setUrl($url);
|
||||
$this->GetUrlOnly();
|
||||
//$process = new Process($this->options);
|
||||
$this->buildCMD();
|
||||
exec($this->cmd, $output, $returnCode);
|
||||
if (count($output) === 1) {
|
||||
return ['url' => reset($output)];
|
||||
}
|
||||
list($url, $filename) = $output;
|
||||
return ['url' => $url, 'filename' => Helper::cleanString($filename)];
|
||||
}
|
||||
|
||||
public function setUrl($url)
|
||||
{
|
||||
$this->addOption('-i');
|
||||
$this->addOption($url);
|
||||
//$index = array_search('-i', $this->options);
|
||||
//array_splice($this->options, $index + 1, 0, $url);
|
||||
}
|
||||
|
||||
public function addOption($option)
|
||||
{
|
||||
array_push($this->options, $option);
|
||||
}
|
||||
|
||||
public function forceIPV4()
|
||||
{
|
||||
$this->addOption('-4');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAudioFormat($format)
|
||||
{
|
||||
$this->audioFormat = $format;
|
||||
}
|
||||
|
||||
public function setvideoFormat($format)
|
||||
{
|
||||
$this->videoFormat = $format;
|
||||
}
|
||||
|
||||
private function buildCMD()
|
||||
{
|
||||
$this->cmd = $this->bin;//. " 2>&1";
|
||||
|
||||
foreach ($this->options as $option) {
|
||||
$this->cmd .= " " . $option;
|
||||
}
|
||||
|
||||
}
|
||||
public function isInstalled()
|
||||
{
|
||||
return (bool) isset($this->bin);
|
||||
}
|
||||
public static function install()
|
||||
{
|
||||
$url = $this->installUrl();
|
||||
$path = \OC::$server->getSystemConfig()->getValue('datadirectory');
|
||||
Helper::Download($url, $path . "/youtube-dl");
|
||||
}
|
||||
|
||||
public function installUrl()
|
||||
{
|
||||
return "https://yt-dl.org/downloads/latest/youtube-dl";
|
||||
}
|
||||
|
||||
}
|
||||
11
lib/Tools/aria2Options.php
Normal file
11
lib/Tools/aria2Options.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace OCA\NcDownloader\Tools;
|
||||
|
||||
class aria2Options
|
||||
{
|
||||
|
||||
public static function get()
|
||||
{
|
||||
return ["ca-certificate", "certificate", "dht-file-path", "dht-file-path6", "dir", "input-file", "load-cookies", "log", "metalink-file", "netrc-path", "on-bt-download-complete", "on-download-complete", "on-download-error", "on-download-start", "on-download-stop", "on-download-pause", "out", "private-key", "rpc-certificate", "rpc-private-key", "save-cookies", "save-session", "server-stat-if", "server-stat-of", "torrent-file", "all-proxy", "all-proxy-passwd", "all-proxy-user", "allow-overwrite", "allow-piece-length-change", "always-resume", "async-dns", "auto-file-renaming", "bt-enable-hook-after-hash-check", "bt-enable-lpd", "bt-exclude-tracker", "bt-external-ip", "bt-force-encryption", "bt-hash-check-seed", "bt-load-saved-metadata", "bt-max-peers", "bt-metadata-only", "bt-min-crypto-level", "bt-prioritize-piece", "bt-remove-unselected-file", "bt-request-peer-speed-limit", "bt-require-crypto", "bt-save-metadata", "bt-seed-unverified", "bt-stop-timeout", "bt-tracker", "bt-tracker-connect-timeout", "bt-tracker-interval", "bt-tracker-timeout", "check-integrity", "checksum", "conditional-get", "connect-timeout", "content-disposition-default-utf8", "continue", "dir", "dry-run", "enable-http-keep-alive", "enable-http-pipelining", "enable-mmap", "enable-peer-exchange", "file-allocation", "follow-metalink", "follow-torrent", "force-save", "ftp-passwd", "ftp-pasv", "ftp-proxy", "ftp-proxy-passwd", "ftp-proxy-user", "ftp-reuse-connection", "ftp-type", "ftp-user", "gid", "hash-check-only", "header", "http-accept-gzip", "http-auth-challenge", "http-no-cache", "http-passwd", "http-proxy", "http-proxy-passwd", "http-proxy-user", "http-user", "https-proxy", "https-proxy-passwd", "https-proxy-user", "index-out", "lowest-speed-limit", "max-connection-per-server", "max-download-limit", "max-file-not-found", "max-mmap-limit", "max-resume-failure-tries", "max-tries", "max-upload-limit", "metalink-base-uri", "metalink-enable-unique-protocol", "metalink-language", "metalink-location", "metalink-os", "metalink-preferred-protocol", "metalink-version", "min-split-size", "no-file-allocation-limit", "no-netrc", "no-proxy", "out", "parameterized-uri", "pause", "pause-metadata", "piece-length", "proxy-method", "realtime-chunk-checksum", "referer", "remote-time", "remove-control-file", "retry-wait", "reuse-uri", "rpc-save-upload-metadata", "seed-ratio", "seed-time", "select-file", "split", "ssh-host-key-md", "stream-piece-selector", "timeout", "uri-selector", "use-head", "user-agent", "dry-run", "metalink-base-uri", "parameterized-uri", "pause", "piece-length", "rpc-save-upload-metadata", "bt-max-peers", "bt-request-peer-speed-limit", "bt-remove-unselected-file", "force-save", "max-download-limit", "max-upload-limit", "bt-max-open-files", "download-result", "keep-unfinished-download-result", "log", "log-level", "max-concurrent-downloads", "max-download-result", "max-overall-download-limit", "max-overall-upload-limit", "optimize-concurrent-downloads", "save-cookies", "save-session", "server-stat-of"];
|
||||
}
|
||||
}
|
||||
26423
package-lock.json
generated
Normal file
26423
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
66
package.json
Executable file
66
package.json
Executable file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "ncdownloader",
|
||||
"description": "Nextcloud Frontend for youtube-dl and Aria2",
|
||||
"version": "1.0.0",
|
||||
"author": "Jiaxin Huang <freefallbenson@gmail.com>",
|
||||
"contributors": [
|
||||
"Jiaxin Huang <freefallbenson@gmail.com>"
|
||||
],
|
||||
"keywords": [
|
||||
"nextcloud",
|
||||
"ncdownloader",
|
||||
"app",
|
||||
"dev"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/shiningw/ncdownloader/issues"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/shiningw/ncdownloader",
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/shiningw/ncdownloader",
|
||||
"license": "agpl",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack --progress --config webpack.app.js",
|
||||
"app": "NODE_ENV=development webpack --progress --config webpack.app.js",
|
||||
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.app.js",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint:fix": "eslint --ext .js,.vue src --fix",
|
||||
"stylelint": "stylelint src",
|
||||
"stylelint:fix": "stylelint src --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/router": "^2.0.0",
|
||||
"bootstrap": "^5.1.0",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"jquery": "^3.6.0",
|
||||
"sass": "^1.38.0",
|
||||
"sass-loader": "^10.2.0",
|
||||
"svg-url-loader": "^7.1.1",
|
||||
"tippy.js": "^6.3.1",
|
||||
"toastify-js": "^1.11.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"validator": "^13.6.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"extends @nextcloud/browserslist-config"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^2.1.0",
|
||||
"@nextcloud/eslint-config": "^6.0.0",
|
||||
"@nextcloud/stylelint-config": "^1.0.0-beta.0",
|
||||
"@nextcloud/webpack-vue-config": "^4.0.3",
|
||||
"babel-loader": "^8.2.2"
|
||||
}
|
||||
}
|
||||
7
phpunit.integration.xml
Normal file
7
phpunit.integration.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<phpunit bootstrap="tests/bootstrap.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="integration">
|
||||
<directory>./tests/Integration</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
7
phpunit.xml
Normal file
7
phpunit.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<phpunit bootstrap="tests/bootstrap.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>./tests/Unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
94
src/OC/msg.js
Normal file
94
src/OC/msg.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import $ from 'jquery'
|
||||
|
||||
/**
|
||||
* A little class to manage a status field for a "saving" process.
|
||||
* It can be used to display a starting message (e.g. "Saving...") and then
|
||||
* replace it with a green success message or a red error message.
|
||||
*
|
||||
* @namespace OC.msg
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Displayes a "Saving..." message in the given message placeholder
|
||||
*
|
||||
* @param {Object} selector Placeholder to display the message in
|
||||
*/
|
||||
startSaving(selector) {
|
||||
this.startAction(selector, t('core', 'Saving …'))
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes a custom message in the given message placeholder
|
||||
*
|
||||
* @param {Object} selector Placeholder to display the message in
|
||||
* @param {string} message Plain text message to display (no HTML allowed)
|
||||
*/
|
||||
startAction(selector, message) {
|
||||
$(selector).text(message)
|
||||
.removeClass('success')
|
||||
.removeClass('error')
|
||||
.stop(true, true)
|
||||
.show()
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success/error message in the given selector
|
||||
*
|
||||
* @param {Object} selector Placeholder to display the message in
|
||||
* @param {Object} response Response of the server
|
||||
* @param {Object} response.data Data of the servers response
|
||||
* @param {string} response.data.message Plain text message to display (no HTML allowed)
|
||||
* @param {string} response.status is being used to decide whether the message
|
||||
* is displayed as an error/success
|
||||
*/
|
||||
finishedSaving(selector, response) {
|
||||
this.finishedAction(selector, response)
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success/error message in the given selector
|
||||
*
|
||||
* @param {Object} selector Placeholder to display the message in
|
||||
* @param {Object} response Response of the server
|
||||
* @param {Object} response.data Data of the servers response
|
||||
* @param {string} response.data.message Plain text message to display (no HTML allowed)
|
||||
* @param {string} response.status is being used to decide whether the message
|
||||
* is displayed as an error/success
|
||||
*/
|
||||
finishedAction(selector, response) {
|
||||
if (response.status === 'success') {
|
||||
this.finishedSuccess(selector, response.data.message)
|
||||
} else {
|
||||
this.finishedError(selector, response.data.message)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an success message in the given selector
|
||||
*
|
||||
* @param {Object} selector Placeholder to display the message in
|
||||
* @param {string} message Plain text success message to display (no HTML allowed)
|
||||
*/
|
||||
finishedSuccess(selector, message) {
|
||||
$(selector).text(message)
|
||||
.addClass('success')
|
||||
.removeClass('error')
|
||||
.stop(true, true)
|
||||
.delay(3000)
|
||||
.fadeOut(900)
|
||||
.show()
|
||||
},
|
||||
|
||||
/**
|
||||
* Displayes an error message in the given selector
|
||||
*
|
||||
* @param {Object} selector Placeholder to display the message in
|
||||
* @param {string} message Plain text error message to display (no HTML allowed)
|
||||
*/
|
||||
finishedError(selector, message) {
|
||||
$(selector).text(message)
|
||||
.addClass('error')
|
||||
.removeClass('success')
|
||||
.show()
|
||||
},
|
||||
}
|
||||
3
src/aria2Options.js
Normal file
3
src/aria2Options.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const aria2Options = ["ca-certificate", "certificate", "dht-file-path", "dht-file-path6", "dir", "input-file", "load-cookies", "log", "metalink-file", "netrc-path", "on-bt-download-complete", "on-download-complete", "on-download-error", "on-download-start", "on-download-stop", "on-download-pause", "out", "private-key", "rpc-certificate", "rpc-private-key", "save-cookies", "save-session", "server-stat-if", "server-stat-of", "torrent-file", "all-proxy", "all-proxy-passwd", "all-proxy-user", "allow-overwrite", "allow-piece-length-change", "always-resume", "async-dns", "auto-file-renaming", "bt-enable-hook-after-hash-check", "bt-enable-lpd", "bt-exclude-tracker", "bt-external-ip", "bt-force-encryption", "bt-hash-check-seed", "bt-load-saved-metadata", "bt-max-peers", "bt-metadata-only", "bt-min-crypto-level", "bt-prioritize-piece", "bt-remove-unselected-file", "bt-request-peer-speed-limit", "bt-require-crypto", "bt-save-metadata", "bt-seed-unverified", "bt-stop-timeout", "bt-tracker", "bt-tracker-connect-timeout", "bt-tracker-interval", "bt-tracker-timeout", "check-integrity", "checksum", "conditional-get", "connect-timeout", "content-disposition-default-utf8", "continue", "dir", "dry-run", "enable-http-keep-alive", "enable-http-pipelining", "enable-mmap", "enable-peer-exchange", "file-allocation", "follow-metalink", "follow-torrent", "force-save", "ftp-passwd", "ftp-pasv", "ftp-proxy", "ftp-proxy-passwd", "ftp-proxy-user", "ftp-reuse-connection", "ftp-type", "ftp-user", "gid", "hash-check-only", "header", "http-accept-gzip", "http-auth-challenge", "http-no-cache", "http-passwd", "http-proxy", "http-proxy-passwd", "http-proxy-user", "http-user", "https-proxy", "https-proxy-passwd", "https-proxy-user", "index-out", "lowest-speed-limit", "max-connection-per-server", "max-download-limit", "max-file-not-found", "max-mmap-limit", "max-resume-failure-tries", "max-tries", "max-upload-limit", "metalink-base-uri", "metalink-enable-unique-protocol", "metalink-language", "metalink-location", "metalink-os", "metalink-preferred-protocol", "metalink-version", "min-split-size", "no-file-allocation-limit", "no-netrc", "no-proxy", "out", "parameterized-uri", "pause", "pause-metadata", "piece-length", "proxy-method", "realtime-chunk-checksum", "referer", "remote-time", "remove-control-file", "retry-wait", "reuse-uri", "rpc-save-upload-metadata", "seed-ratio", "seed-time", "select-file", "split", "ssh-host-key-md", "stream-piece-selector", "timeout", "uri-selector", "use-head", "user-agent", "dry-run", "metalink-base-uri", "parameterized-uri", "pause", "piece-length", "rpc-save-upload-metadata", "bt-max-peers", "bt-request-peer-speed-limit", "bt-remove-unselected-file", "force-save", "max-download-limit", "max-upload-limit", "bt-max-open-files", "download-result", "keep-unfinished-download-result", "log", "log-level", "max-concurrent-downloads", "max-download-result", "max-overall-download-limit", "max-overall-upload-limit", "optimize-concurrent-downloads", "save-cookies", "save-session", "server-stat-of"];
|
||||
|
||||
export default aria2Options
|
||||
306
src/autoComplete.js
Normal file
306
src/autoComplete.js
Normal file
@@ -0,0 +1,306 @@
|
||||
class autoComplete {
|
||||
options;
|
||||
static UP = 38;
|
||||
static DOWN = 40;
|
||||
static ENTER = 13;
|
||||
static ESC = 27;
|
||||
static entryClass = ".suggestion-item";
|
||||
static entryClassContainer = ".suggestion-container";
|
||||
constructor(options) {
|
||||
this.options = {
|
||||
selector: 0,
|
||||
source: 0,
|
||||
minChars: 3,
|
||||
delay: 150,
|
||||
offsetLeft: 0,
|
||||
offsetTop: 1,
|
||||
cache: 1,
|
||||
menuClass: '',
|
||||
renderItem: function (item, search) {
|
||||
search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
var re = new RegExp(`(${search.split(' ').join('|')})`, "gi");
|
||||
return `<div class="suggestion-item" data-val="${item}">${item.replace(re, "<b>$1</b>")}</div>`;
|
||||
},
|
||||
onSelect: function (e, term, item) { }
|
||||
};
|
||||
for (let k in this.options) {
|
||||
if (options.hasOwnProperty(k)) this.options[k] = options[k];
|
||||
}
|
||||
if (typeof this.options.selector !== 'string' && !(this.options.selector instanceof NodeList))
|
||||
throw ("invalid selecor!");
|
||||
this.elements = typeof this.options.selector == 'object' ? this.options.selector : document.querySelectorAll(this.options.selector);
|
||||
}
|
||||
static getInstance(options) {
|
||||
return new autoComplete(options);
|
||||
}
|
||||
attachData(element) {
|
||||
element.rect = element.getBoundingClientRect();
|
||||
element.sgBox = this.createSuggestionBox();
|
||||
element.options = this.options;
|
||||
}
|
||||
run() {
|
||||
for (const element of this.elements) {
|
||||
this.init(element);
|
||||
}
|
||||
}
|
||||
|
||||
init(element) {
|
||||
element.autocompleteAttr = element.getAttribute('autocomplete');
|
||||
element.setAttribute('autocomplete', 'off');
|
||||
element.cache = {};
|
||||
element.lastValue = '';
|
||||
this.attachData(element);
|
||||
this.attach('resize', window, function (e) {
|
||||
autoComplete.updateSuggestionBox(element);
|
||||
});
|
||||
document.body.appendChild(element.sgBox);
|
||||
this.live('suggestion-item', 'mouseleave', function (e) {
|
||||
var sel = element.sgBox.querySelector('.suggestion-item.selected');
|
||||
if (sel)
|
||||
setTimeout(function () { sel.className = sel.className.replace('selected', ''); }, 20);
|
||||
}, element.sgBox);
|
||||
|
||||
this.live('suggestion-item', 'mouseover', function (e) {
|
||||
var sel = element.sgBox.querySelector('.suggestion-item.selected');
|
||||
if (sel) {
|
||||
sel.classList.remove("selected");
|
||||
}
|
||||
this.className += ' selected';
|
||||
}, element.sgBox);
|
||||
const selectHandler = function (selected, element, e) {
|
||||
if (autoComplete.hasClass(selected, 'suggestion-item')) {
|
||||
let v = selected.getAttribute('data-val');
|
||||
element.value = v;
|
||||
element.options.onSelect(e, v, selected);
|
||||
element.sgBox.style.display = 'none';
|
||||
}
|
||||
}
|
||||
this.live('suggestion-item', 'mousedown,pointerdown', function (e) {
|
||||
e.stopPropagation();
|
||||
//this refers to the found element within;
|
||||
let selected = this;
|
||||
selectHandler(selected, element, e);
|
||||
}, element.sgBox);
|
||||
|
||||
this.attach('blur', element, autoComplete.blurCallback);
|
||||
this.attach('keydown', element, autoComplete.keyDownCallback);
|
||||
this.attach('keyup', element, autoComplete.keyUpCallback);
|
||||
if (!this.options.minChars)
|
||||
this.attach('focus', element, autoComplete.focusCallback);
|
||||
}
|
||||
static suggest(element, data) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
let sgBox = element.sgBox, options = element.options;
|
||||
var val = element.value;
|
||||
element.cache[val] = data;
|
||||
if (data.length && val.length >= options.minChars) {
|
||||
var s = '';
|
||||
for (var i = 0; i < data.length; i++) s += options.renderItem(data[i], val);
|
||||
sgBox.innerHTML = s;
|
||||
autoComplete.updateSuggestionBox(element, 0);
|
||||
}
|
||||
else {
|
||||
sgBox.style.display = 'none';
|
||||
}
|
||||
}
|
||||
static hasClass(el, className) {
|
||||
return el.classList ? el.classList.contains(className) : new RegExp('\\b' + className + '\\b').test(el.className);
|
||||
}
|
||||
attach(eventType, target, selector, callback) {
|
||||
if (typeof selector === 'function' && !callback) {
|
||||
callback = selector;
|
||||
selector = target;
|
||||
}
|
||||
if (typeof target === 'object') {
|
||||
if (target.attachEvent) {
|
||||
target.attachEvent('on' + eventType, function (e) {
|
||||
callback.call(target, e);
|
||||
});
|
||||
}
|
||||
else {
|
||||
target.addEventListener(eventType, function (e) {
|
||||
callback.call(target, e);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
let el = document.querySelector(target);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.addEventListener(eventType, function (e) {
|
||||
let element = e.target;
|
||||
if (element === this) {
|
||||
callback.call(element, e);
|
||||
return;
|
||||
}
|
||||
for (; element && element != this; element = element.parentNode) {
|
||||
if (element.matches(selector)) {
|
||||
callback.call(element, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
live(elClass, event, cb, context) {
|
||||
if (typeof event === 'string' && event.indexOf(',')) {
|
||||
var events = event.split(',');
|
||||
}
|
||||
for (const event of events) {
|
||||
this.attach(event, context || document, function (e) {
|
||||
var found, el = e.target || e.srcElement;
|
||||
while (el && !(found = autoComplete.hasClass(el, elClass)))
|
||||
el = el.parentElement;
|
||||
if (found) cb.call(el, e);
|
||||
});
|
||||
}
|
||||
}
|
||||
static blurCallback(e) {
|
||||
let element = this
|
||||
let sgBox = element.sgBox;
|
||||
let hoverActive;
|
||||
try {
|
||||
hoverActive = document.querySelector('.suggestion-container:hover');
|
||||
} catch (e) {
|
||||
hoverActive = 0;
|
||||
}
|
||||
if (!hoverActive) {
|
||||
element.lastValue = element.value;
|
||||
sgBox.style.display = 'none';
|
||||
setTimeout(function () { sgBox.style.display = 'none'; }, 350);
|
||||
} else if (element !== document.activeElement) {
|
||||
setTimeout(function () {
|
||||
element.focus();
|
||||
}, 20);
|
||||
}
|
||||
}
|
||||
static keyUpCallback(e) {
|
||||
let element = this;
|
||||
let sgBox = element.sgBox, options = element.options;
|
||||
var key = window.event ? e.keyCode : e.which;
|
||||
if (!key || (key < 35 || key > 40) && ![autoComplete.ENTER, autoComplete.ESC].includes(key)) {
|
||||
var val = element.value;
|
||||
if (val.length >= options.minChars) {
|
||||
if (val != element.lastValue) {
|
||||
element.lastValue = val;
|
||||
clearTimeout(element.timer);
|
||||
if (options.cache) {
|
||||
if (val in element.cache) {
|
||||
autoComplete.suggest(element, element.cache[val]); return;
|
||||
}
|
||||
for (var i = 1; i < val.length - options.minChars; i++) {
|
||||
var part = val.slice(0, val.length - i);
|
||||
if (part in element.cache && !element.cache[part].length) {
|
||||
autoComplete.suggest([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const msuggest = function (data) {
|
||||
autoComplete.suggest(element, data);
|
||||
}
|
||||
element.timer = setTimeout(function () { options.source(val, msuggest) }, options.delay);
|
||||
}
|
||||
} else {
|
||||
element.lastValue = val;
|
||||
sgBox.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static keyDownCallback(e) {
|
||||
let element = this;
|
||||
let sgBox = element.sgBox, options = element.options;
|
||||
var key = window.event ? e.keyCode : e.which;
|
||||
// down =40, up =38
|
||||
if ((key == 40 || key == 38) && sgBox.innerHTML) {
|
||||
var next, sel = sgBox.querySelector('.suggestion-item.selected');
|
||||
if (!sel) {
|
||||
next = (key == 40) ? sgBox.querySelector('.suggestion-item') : sgBox.childNodes[scBox.childNodes.length - 1]; // first : last
|
||||
next.className += ' selected';
|
||||
element.value = next.getAttribute('data-val');
|
||||
} else {
|
||||
next = (key == 40) ? sel.nextSibling : sel.previousSibling;
|
||||
if (next) {
|
||||
sel.className = sel.className.replace('selected', '');
|
||||
next.className += ' selected';
|
||||
element.value = next.getAttribute('data-val');
|
||||
}
|
||||
else {
|
||||
sel.className = sel.className.replace('selected', '');
|
||||
element.value = element.lastValue; next = 0;
|
||||
}
|
||||
}
|
||||
autoComplete.updateSuggestionBox(element, 0, next);
|
||||
return false;
|
||||
//ESC = 27
|
||||
} else if (key == 27) {
|
||||
element.value = element.lastValue;
|
||||
sgBox.style.display = 'none';
|
||||
//enter = 13,tab = 9
|
||||
} else if (key == 13 || key == 9) {
|
||||
var sel = sgBox.querySelector('.suggestion-item.selected');
|
||||
if (sel && sgBox.style.display != 'none') {
|
||||
options.onSelect(e, sel.getAttribute('data-val'), sel);
|
||||
setTimeout(function () { sgBox.style.display = 'none'; }, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static focusCallback(e) {
|
||||
let element = this;
|
||||
element.lastValue = '\n';
|
||||
autoComplete.keyUpCallback(e)
|
||||
}
|
||||
|
||||
static getMaxHeight(element) {
|
||||
let style = window.getComputedStyle ? getComputedStyle(element, null) : element.currentStyle;
|
||||
return parseInt(style.maxHeight);
|
||||
}
|
||||
|
||||
static updatePosition(element, rect, options) {
|
||||
element.style.left = Math.round(rect.left + (window.pageXOffset || document.documentElement.scrollLeft) + options.offsetLeft) + 'px';
|
||||
element.style.top = Math.round(rect.bottom + (window.pageYOffset || document.documentElement.scrollTop) + options.offsetTop) + 'px';
|
||||
element.style.width = Math.round(rect.right - rect.left) + 'px';
|
||||
}
|
||||
|
||||
static updateSuggestionBox(element, resize, next) {
|
||||
let sgBox = element.sgBox, rect = element.getBoundingClientRect(), options = element.options;
|
||||
autoComplete.updatePosition(sgBox, rect, options);
|
||||
if (resize && !next) {
|
||||
return;
|
||||
}
|
||||
sgBox.style.display = 'block';
|
||||
if (!sgBox.maxHeight) {
|
||||
sgBox.maxHeight = autoComplete.getMaxHeight(sgBox);
|
||||
}
|
||||
if (!sgBox.suggestionHeight) {
|
||||
sgBox.suggestionHeight = sgBox.querySelector('.suggestion-item').offsetHeight;
|
||||
}
|
||||
|
||||
if (!sgBox.suggestionHeight) {
|
||||
return;
|
||||
}
|
||||
if (!next) {
|
||||
sgBox.scrollTop = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let scrTop = sgBox.scrollTop
|
||||
let selTop = next.getBoundingClientRect().top - sgBox.getBoundingClientRect().top;
|
||||
if (selTop + sgBox.suggestionHeight - sgBox.maxHeight > 0) {
|
||||
sgBox.scrollTop = selTop + sgBox.suggestionHeight + scrTop - sgBox.maxHeight;
|
||||
} else if (selTop < 0) {
|
||||
sgBox.scrollTop = selTop + scrTop;
|
||||
}
|
||||
}
|
||||
createSuggestionBox() {
|
||||
let sgBox = document.createElement('div');
|
||||
sgBox.classList.add('suggestion-container');
|
||||
//suggestionBox.classList.add(options.menuClass);
|
||||
return sgBox;
|
||||
}
|
||||
}
|
||||
export default autoComplete;
|
||||
39
src/buttonActions.js
Normal file
39
src/buttonActions.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import $ from 'jquery'
|
||||
import Http from './http'
|
||||
import helper from './helper'
|
||||
const buttonHandler = (event, type) => {
|
||||
let element = event.target;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let url = element.getAttribute("path");
|
||||
let row, data = {};
|
||||
let removeRow = true;
|
||||
if (row = element.closest('.table-row-search')) {
|
||||
data['form_input_text'] = row.dataset.link;
|
||||
} else {
|
||||
row = element.closest('.table-row')
|
||||
data = row.dataset;
|
||||
if (!data.gid) {
|
||||
console.log("gid is not set!");
|
||||
}
|
||||
}
|
||||
Http.getInstance(url).setErrorHandler(function (xhr, textStatus, error) {
|
||||
console.log(error);
|
||||
}).setHandler(function (data) {
|
||||
if (data.hasOwnProperty('error')) {
|
||||
helper.message(data['error']);
|
||||
return;
|
||||
}
|
||||
if (data.hasOwnProperty('result')) {
|
||||
helper.message("Success for " + data['result']);
|
||||
}
|
||||
if (row && removeRow)
|
||||
row.remove();
|
||||
}).setData(data).send();
|
||||
|
||||
}
|
||||
export default {
|
||||
run: function () {
|
||||
$("#ncdownloader-table-wrapper").on("click", ".table-cell-action-item .button-container button", e => buttonHandler(e, ''));
|
||||
}
|
||||
}
|
||||
42
src/eventHandler.js
Normal file
42
src/eventHandler.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const eventHandler = {
|
||||
add: function (eventType, target, selector, callback) {
|
||||
if (typeof selector === 'function' && !callback) {
|
||||
callback = selector;
|
||||
selector = target;
|
||||
}
|
||||
if (typeof target === 'object') {
|
||||
if (target.attachEvent) {
|
||||
target.attachEvent('on' + eventType, function (e) {
|
||||
callback.call(target, e);
|
||||
});
|
||||
}
|
||||
else {
|
||||
target.addEventListener(eventType, function (e) {
|
||||
callback.call(target, e);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
let el = document.querySelector(target);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.addEventListener(eventType, function (e) {
|
||||
let element = e.target;
|
||||
if (element === this && selector === target) {
|
||||
callback.call(element, e);
|
||||
return;
|
||||
}
|
||||
for (; element && element != this; element = element.parentNode) {
|
||||
if (element.matches(selector)) {
|
||||
callback.call(element, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
off: function (element, eventType, callback) {
|
||||
element.removeEventListener(eventType, callback);
|
||||
}
|
||||
}
|
||||
export default eventHandler;
|
||||
149
src/helper.js
Normal file
149
src/helper.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import $ from 'jquery'
|
||||
import {
|
||||
generateUrl
|
||||
} from '@nextcloud/router'
|
||||
import Toastify from 'toastify-js'
|
||||
import "toastify-js/src/toastify.css"
|
||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||
import nctable from './ncTable';
|
||||
import Http from './http'
|
||||
|
||||
const helper = {
|
||||
generateUrl: generateUrl,
|
||||
loop(callback, delay, ...args) {
|
||||
callback(...args);
|
||||
clearTimeout(helper.timeoutID);
|
||||
this.polling(callback, delay, ...args);
|
||||
},
|
||||
enabledPolling: 1,
|
||||
trim(string, char) {
|
||||
return string.split(char).filter(Boolean).join(char)
|
||||
},
|
||||
isHtml(string) {
|
||||
const htmlRegex = new RegExp('^<([a-z]+)[^>]+>(.*?)</\\1>', 'i');
|
||||
return htmlRegex.test(string);
|
||||
},
|
||||
ucfirst(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
},
|
||||
polling(callback, delay, ...args) {
|
||||
self = this;
|
||||
helper.timeoutID = setTimeout(function () {
|
||||
if (self.enabledPolling) {
|
||||
callback(...args);
|
||||
self.polling(callback, delay, ...args);
|
||||
}
|
||||
}, delay);
|
||||
},
|
||||
isURL(url) {
|
||||
const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
|
||||
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
|
||||
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
|
||||
'(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
|
||||
return pattern.test(url);
|
||||
},
|
||||
isMagnetURI(url) {
|
||||
const magnetURI = /^magnet:\?xt=urn:[a-z0-9]+:[a-z0-9]{32,40}&dn=.+&tr=.+$/i;
|
||||
|
||||
return magnetURI.test(url.trim());
|
||||
},
|
||||
message: function (message) {
|
||||
Toastify({
|
||||
text: message,
|
||||
duration: 3000,
|
||||
newWindow: true,
|
||||
close: true,
|
||||
gravity: "top", // `top` or `bottom`
|
||||
position: "center", // `left`, `center` or `right`
|
||||
backgroundColor: "linear-gradient(to right, rgb(26, 28, 27), rgb(126, 138, 105))",
|
||||
stopOnFocus: true, // Prevents dismissing of toast on hover
|
||||
onClick: function () { } // Callback after click
|
||||
}).showToast();
|
||||
},
|
||||
aria2Toggle: function (data) {
|
||||
if (!data.status) {
|
||||
return;
|
||||
}
|
||||
if (!data.status && data.error) {
|
||||
return;
|
||||
}
|
||||
let $element = $("#start-aria2 button");
|
||||
let aria2 = $element.attr("data-aria2");
|
||||
if (aria2 === 'on') {
|
||||
$element.attr("data-aria2", "off").html(t("ncdownloader", "Start Aria2"));
|
||||
} else {
|
||||
$element.attr("data-aria2", "on").html(t("ncdownloader", "Stop Aria2"));
|
||||
}
|
||||
},
|
||||
getPathLast: function (path) {
|
||||
return path.substring(path.lastIndexOf('/') + 1)
|
||||
},
|
||||
updateCounter(data) {
|
||||
for (let key in data) {
|
||||
const counter = document.getElementById(key + "-downloads-counter")
|
||||
counter.innerHTML = '<div class="number">' + data[key] + '</div>';
|
||||
}
|
||||
},
|
||||
refresh(path) {
|
||||
path = path || "/apps/ncdownloader/status/active";
|
||||
let url = helper.generateUrl(path);
|
||||
Http.getInstance(url).setHandler(function (data) {
|
||||
if (data && data.row) {
|
||||
nctable.getInstance(data.title, data.row).create();
|
||||
} else {
|
||||
nctable.getInstance().noData();
|
||||
}
|
||||
if (data.counter)
|
||||
helper.updateCounter(data.counter);
|
||||
}).send();
|
||||
},
|
||||
html2DOM: function (htmlString) {
|
||||
const parser = new window.DOMParser();
|
||||
let doc = parser.parseFromString(htmlString, "text/html")
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
makePair: function (data, prefix = "aria2-settings") {
|
||||
for (let key in data) {
|
||||
let index;
|
||||
if ((index = key.indexOf(prefix + "-key-")) !== -1) {
|
||||
let valueKey = prefix + "-value-" + key.substring(key.lastIndexOf('-') + 1);
|
||||
if (!data[valueKey]) continue;
|
||||
let newkey = data[key];
|
||||
data[newkey] = data[valueKey];
|
||||
delete data[key];
|
||||
delete data[valueKey];
|
||||
}
|
||||
}
|
||||
},
|
||||
getData(selector) {
|
||||
const element = typeof selector === "object" ? selector : document.getElementById(selector)
|
||||
const data = {}
|
||||
data['path'] = element.getAttribute('path') || '';
|
||||
//if the targeted element is not of input or select type, search for such elements below it
|
||||
if (!['SELECT', 'INPUT'].includes(element.nodeName.toUpperCase())) {
|
||||
const nodeList = element.querySelectorAll('input,select')
|
||||
|
||||
for (let i = 0; i < nodeList.length; i++) {
|
||||
const element = nodeList[i]
|
||||
if (element.hasAttribute('type') && element.getAttribute('type') === 'button') {
|
||||
continue
|
||||
}
|
||||
const key = element.getAttribute('id')
|
||||
data[key] = element.value
|
||||
for (let prop in element.dataset) {
|
||||
data[prop] = element.dataset[prop];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let prop in element.dataset) {
|
||||
data[prop] = element.dataset[prop];
|
||||
}
|
||||
const key = element.getAttribute('id')
|
||||
data[key] = element.value
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
export default helper
|
||||
52
src/http.js
Normal file
52
src/http.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const Http = class {
|
||||
data;
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
this.method = 'POST';
|
||||
this.data = null;
|
||||
this.dataType = 'application/json';
|
||||
this.xhr = new XMLHttpRequest();
|
||||
}
|
||||
static getInstance(url) {
|
||||
return new Http(url);
|
||||
}
|
||||
setData(data) {
|
||||
this.data = data
|
||||
return this
|
||||
}
|
||||
send() {
|
||||
let token = this.getToken();
|
||||
this.xhr.open(this.method, this.url);
|
||||
this.xhr.setRequestHeader('requesttoken', token)
|
||||
this.xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||
this.xhr.setRequestHeader('Content-Type', this.dataType);
|
||||
let callback = this.handler;
|
||||
this.xhr.onload = () => {
|
||||
if (typeof callback === 'function')
|
||||
callback(JSON.parse(this.xhr.response));
|
||||
}
|
||||
this.xhr.onerror = this.errorHandler;
|
||||
this.xhr.send(JSON.stringify(this.data));
|
||||
}
|
||||
getToken() {
|
||||
return document.getElementsByTagName('head')[0].getAttribute('data-requesttoken')
|
||||
}
|
||||
setUrl(url) {
|
||||
this.url = url
|
||||
return this
|
||||
}
|
||||
setMethod(method) {
|
||||
this.method = method
|
||||
return this
|
||||
}
|
||||
setHandler(handler) {
|
||||
this.handler = handler || function (data) { };
|
||||
return this;
|
||||
}
|
||||
setErrorHandler(handler) {
|
||||
this.errorHandler = handler
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export default Http
|
||||
48
src/index.js
Normal file
48
src/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import helper from './helper'
|
||||
import $ from 'jquery'
|
||||
import Http from './http'
|
||||
//import actionLinks from './actionLinks'
|
||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||
import inputAction from './inputAction'
|
||||
import updatePage from './updatePage'
|
||||
import buttonActions from './buttonActions'
|
||||
import inputBox from './inputBox'
|
||||
'use strict'
|
||||
const basePath = "/apps/ncdownloader";
|
||||
$(document).on('ajaxSend', function (elm, xhr, settings) {
|
||||
let token = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken')
|
||||
if (settings.crossDomain === false) {
|
||||
xhr.setRequestHeader('requesttoken', token)
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||
}
|
||||
})
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
inputAction.run();
|
||||
updatePage.run();
|
||||
buttonActions.run();
|
||||
|
||||
$("#ncdownloader-form-wrapper").append(inputBox.getInstance(t("ncdownloader", 'New Download')).create());
|
||||
$("#start-aria2").on("click", function (e) {
|
||||
const path = basePath + "/aria2/start";
|
||||
let url = helper.generateUrl(path);
|
||||
Http.getInstance(url).setHandler(function (data) {
|
||||
helper.aria2Toggle(data);
|
||||
}).send();
|
||||
})
|
||||
|
||||
$('#ncdownloader-user-settings button').on("click", function (e) {
|
||||
let link = helper.generateUrl(e.target.getAttribute('path'));
|
||||
window.location.href = link;
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
|
||||
104
src/inputAction.js
Normal file
104
src/inputAction.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import helper from './helper'
|
||||
import $ from 'jquery'
|
||||
import Http from './http'
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||
import inputBox from './inputBox'
|
||||
import nctable from './ncTable'
|
||||
|
||||
const basePath = "/apps/ncdownloader";
|
||||
|
||||
const createInputBox = (event, type) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
//let id = event.target.closest("div").getAttribute('id');
|
||||
let inputID = event.target.closest("div").dataset.inputbox;
|
||||
let inputElement = inputID ? document.getElementById(inputID) : null;
|
||||
if (inputElement) {
|
||||
inputElement.remove();
|
||||
}
|
||||
let height = $(window).scrollTop();
|
||||
if (height > 50)
|
||||
$("html, body").animate({ scrollTop: 0 }, "fast");
|
||||
let name;
|
||||
switch (type) {
|
||||
case "ytdl":
|
||||
name = t("ncdownloader", 'YTDL Download');
|
||||
break;
|
||||
case "search":
|
||||
name = t("ncdownloader", 'Search');
|
||||
break;
|
||||
default:
|
||||
name = t("ncdownloader", 'New Download');
|
||||
}
|
||||
let container;
|
||||
if (type === 'search') {
|
||||
container = inputBox.getInstance(name, type).addSpinner().create();
|
||||
//container.appendChild(inputBox.createLoading());
|
||||
} else {
|
||||
container = inputBox.getInstance(name, type).create();
|
||||
}
|
||||
$("#ncdownloader-form-wrapper").append(container);
|
||||
}
|
||||
|
||||
const toggleButton = element => {
|
||||
if (!element.previousSibling) {
|
||||
return;
|
||||
}
|
||||
if (element.style.display === 'none') {
|
||||
element.style.display = 'block'
|
||||
element.previousSibling.style.display = 'none';
|
||||
} else {
|
||||
element.style.display = 'none'
|
||||
element.previousSibling.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
const inputHandler = (event) => {
|
||||
event.preventDefault();
|
||||
let element = event.target;
|
||||
// element.textContent = '';
|
||||
//$(element).append(inputBox.createLoading());
|
||||
toggleButton(element);
|
||||
let inputData = helper.getData('form-input-wrapper');
|
||||
let inputValue = inputData.form_input_text;
|
||||
if (inputData.type !== 'search' && !helper.isURL(inputValue) && !helper.isMagnetURI(inputValue)) {
|
||||
helper.message(t("ncdownloader", "Invalid url"));
|
||||
return;
|
||||
}
|
||||
if (inputData.type === 'ytdl') {
|
||||
helper.message(t("ncdownloader", "YTDL Download initiated"));
|
||||
}
|
||||
if (inputData.type === 'search') {
|
||||
nctable.getInstance().loading();
|
||||
}
|
||||
const successCallback = (data, element) => {
|
||||
//data = JSON.parse(data.target.response)
|
||||
if (data !== null && data.hasOwnProperty("file")) {
|
||||
helper.message(t("ncdownloader", "Downloading" + " " + data.file));
|
||||
}
|
||||
toggleButton(element);
|
||||
if (data && data.title) {
|
||||
helper.enabledPolling = 0;
|
||||
const tableInst = nctable.getInstance(data.title, data.row);
|
||||
tableInst.actionLink = false;
|
||||
tableInst.rowClass = "table-row-search";
|
||||
tableInst.create();
|
||||
}
|
||||
}
|
||||
const path = inputData.path || basePath + "/new";
|
||||
let url = helper.generateUrl(path);
|
||||
Http.getInstance(url).setData(inputData).setHandler(function (data) {
|
||||
successCallback(data, element);
|
||||
}).send();
|
||||
}
|
||||
|
||||
export default {
|
||||
run: function () {
|
||||
$("#app-navigation").on("click", "#new-download-ytdl", (event) => createInputBox(event, 'ytdl'));
|
||||
$("#app-navigation").on("click", "#new-download", (event) => createInputBox(event, ''));
|
||||
$("#app-navigation").on("click", "#torrent-search-button", (event) => createInputBox(event, 'search'));
|
||||
|
||||
$("#ncdownloader-form-wrapper").on("click", "#form-input-button", (event) => inputHandler(event))
|
||||
}
|
||||
}
|
||||
62
src/inputBox.js
Normal file
62
src/inputBox.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||
import helper from './helper'
|
||||
|
||||
|
||||
class inputBox {
|
||||
constructor(name, id) {
|
||||
this.name = name;
|
||||
this.container = this._createForm();
|
||||
this.textInput = this._createTextInput(id);
|
||||
this.controlsContainer = this._createControlsContainer();
|
||||
}
|
||||
static getInstance(name, id) {
|
||||
return new inputBox(name, id);
|
||||
}
|
||||
create() {
|
||||
this.container.appendChild(this.textInput);
|
||||
this.controlsContainer.appendChild(this._createControls());
|
||||
this.container.appendChild(this.controlsContainer);
|
||||
return this.container;
|
||||
}
|
||||
_createControlsContainer() {
|
||||
let div = document.createElement("div");
|
||||
div.classList.add("controls-container");
|
||||
return div;
|
||||
}
|
||||
_createForm() {
|
||||
let container = document.createElement("form");
|
||||
container.classList.add("form-input-wrapper");
|
||||
container.setAttribute('id', 'form-input-wrapper');
|
||||
return container;
|
||||
}
|
||||
_createTextInput(id) {
|
||||
id = id || 'general';
|
||||
let textInput = document.createElement('input');
|
||||
textInput.setAttribute('type', 'text');
|
||||
textInput.setAttribute('id', "form_input_text");
|
||||
textInput.setAttribute('data-type', id);
|
||||
textInput.setAttribute('value', '');
|
||||
textInput.classList.add('form-input-text');
|
||||
return textInput;
|
||||
}
|
||||
_createControls() {
|
||||
let button = document.createElement('button');
|
||||
button.setAttribute('type', this.name);
|
||||
button.setAttribute('id', 'form-input-button');
|
||||
//buttonInput.setAttribute('value', t('ncdownloader', helper.ucfirst(name)));
|
||||
let text = document.createTextNode(t('ncdownloader', helper.ucfirst(this.name)));
|
||||
button.appendChild(text);
|
||||
return button;
|
||||
}
|
||||
addSpinner() {
|
||||
const parser = new window.DOMParser();
|
||||
let htmlString = '<button class="bs-spinner"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" disabled></span><span class="visually-hidden">Loading...</span></button>'
|
||||
let doc = parser.parseFromString(htmlString, "text/html")
|
||||
let element = doc.querySelector(".bs-spinner");
|
||||
element.style.display = 'none';
|
||||
this.controlsContainer.appendChild(element);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
export default inputBox;
|
||||
145
src/ncTable.js
Normal file
145
src/ncTable.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import helper from './helper'
|
||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||
|
||||
class ncTable {
|
||||
actionLink = true;
|
||||
bodyClass = "ncdownloader-table-data";
|
||||
rowClass = "table-row";
|
||||
headingClass = "table-heading";
|
||||
cellClass = "table-cell";
|
||||
//this is the parent element the table is going to append to
|
||||
tableContainer = 'ncdownloader-table-wrapper';
|
||||
numRow;
|
||||
table;
|
||||
|
||||
constructor(heading, rows) {
|
||||
this.table = document.getElementById(this.tableContainer);
|
||||
if (heading && rows) {
|
||||
this.table.innerHTML = '';
|
||||
this.rows = rows;
|
||||
this.heading = heading;
|
||||
this.actionButtons = [];
|
||||
}
|
||||
}
|
||||
static getInstance(heading, row) {
|
||||
return new ncTable(heading, row);
|
||||
}
|
||||
create() {
|
||||
let thead = this.createHeading()
|
||||
let tbody = this.createRow();
|
||||
this.table.appendChild(thead);
|
||||
this.table.appendChild(tbody);
|
||||
return this;
|
||||
}
|
||||
clear() {
|
||||
this.table.innerHTML = '';
|
||||
}
|
||||
loading() {
|
||||
let htmlStr = '<div class="text-center"><div class="spinner-border" role="status"> <span class="visually-hidden">Loading...</span></div></div>'
|
||||
this.table.innerHTML = htmlStr;
|
||||
return this;
|
||||
}
|
||||
noData() {
|
||||
this.clear();
|
||||
let div = document.createElement('div');
|
||||
div.classList.add("no-items");
|
||||
div.appendChild(document.createTextNode(t("ncdownloader", 'No items')));
|
||||
this.table.appendChild(div);
|
||||
}
|
||||
createHeading(prefix = "table-heading") {
|
||||
let thead = document.createElement("section");
|
||||
thead.classList.add(this.headingClass);
|
||||
let headRow = document.createElement("header");
|
||||
headRow.classList.add(this.rowClass);
|
||||
thead.classList.add(this.headingClass);
|
||||
this.heading.forEach(name => {
|
||||
let rowItem = document.createElement("div");
|
||||
rowItem.classList.add(prefix + "-" + name.toLowerCase());
|
||||
rowItem.classList.add(this.cellClass);
|
||||
let text = document.createTextNode(t("ncdownloader", helper.ucfirst(name)));
|
||||
rowItem.appendChild(text);
|
||||
headRow.appendChild(rowItem);
|
||||
})
|
||||
thead.appendChild(headRow);
|
||||
return thead;
|
||||
}
|
||||
createRow() {
|
||||
let tbody = document.createElement("section");
|
||||
tbody.classList.add(this.bodyClass);
|
||||
tbody.classList.add("table-body");
|
||||
let row;
|
||||
for (const element of this.rows) {
|
||||
if (element === null) {
|
||||
continue;
|
||||
}
|
||||
row = document.createElement("div");
|
||||
row.classList.add(this.rowClass);
|
||||
let text;
|
||||
for (let key in element) {
|
||||
if (key.substring(0, 4) == 'data') {
|
||||
let name = key.replace("_", "-");
|
||||
row.setAttribute(name, element[key]);
|
||||
row.setAttribute("id", element[key]);
|
||||
continue;
|
||||
}
|
||||
let rowItem = document.createElement("div");
|
||||
rowItem.classList.add(this.cellClass);
|
||||
if (key === 'actions') {
|
||||
rowItem.classList.add([this.cellClass, "action-item"].join("-"));
|
||||
let container = document.createElement("div");
|
||||
container.classList.add("button-container");
|
||||
element[key].forEach(value => {
|
||||
container.appendChild(this.createActionButton(value.name, value.path));
|
||||
})
|
||||
rowItem.appendChild(container);
|
||||
row.appendChild(rowItem);
|
||||
continue;
|
||||
}
|
||||
if (typeof element[key] === 'object') {
|
||||
let child = element[key];
|
||||
let div;
|
||||
child.forEach(ele => {
|
||||
div = document.createElement('div');
|
||||
if (helper.isHtml(ele)) {
|
||||
div.innerHTML = ele;
|
||||
} else {
|
||||
text = document.createTextNode(ele);
|
||||
div.appendChild(text);
|
||||
}
|
||||
rowItem.appendChild(div);
|
||||
})
|
||||
rowItem.setAttribute("id", [this.cellClass, key].join("-"));
|
||||
row.appendChild(rowItem);
|
||||
continue;
|
||||
}
|
||||
text = document.createTextNode(element[key]);
|
||||
rowItem.appendChild(text);
|
||||
rowItem.setAttribute("id", [this.cellClass, key].join("-"));
|
||||
row.appendChild(rowItem);
|
||||
}
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
return tbody;
|
||||
|
||||
}
|
||||
|
||||
createActionButton(name, path) {
|
||||
let button = document.createElement("button");
|
||||
button.classList.add("icon-" + name);
|
||||
button.setAttribute("path", path);
|
||||
return button;
|
||||
}
|
||||
|
||||
createActionCell(cell) {
|
||||
let div = document.createElement("div");
|
||||
let button = document.createElement("button");
|
||||
button.classList.add("icon-more", "action-button");
|
||||
button.setAttribute("id", "action-links-button");
|
||||
div.classList.add("action-item");
|
||||
div.appendChild(button);
|
||||
//div.appendChild(actionLinks);
|
||||
cell.appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
export default ncTable;
|
||||
105
src/settings.js
Normal file
105
src/settings.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import Http from './http'
|
||||
import OC_msg from './OC/msg'
|
||||
import {
|
||||
generateUrl
|
||||
} from '@nextcloud/router'
|
||||
import settingsForm from './settingsForm'
|
||||
import autoComplete from './autoComplete';
|
||||
import eventHandler from './eventHandler';
|
||||
import aria2Options from './aria2Options';
|
||||
import helper from './helper';
|
||||
'use strict';
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
eventHandler.add('click', '.ncdownloader-admin-settings', 'input[type="button"]', function (event) {
|
||||
e.stopPropagation();
|
||||
OC_msg.startSaving('#ncdownloader-message-banner');
|
||||
const target = this.getAttribute("data-rel");
|
||||
const path = inputData.url || "/apps/ncdownloader/admin/save";
|
||||
let url = generateUrl(path);
|
||||
Http.getInstance(url).setData(helper.getData(target)).setHandler(function () {
|
||||
OC_msg.finishedSuccess('#ncdownloader-message-banner', "OK");
|
||||
}).send();
|
||||
});
|
||||
eventHandler.add('click', '.ncdownloader-personal-settings', 'input[type="button"]', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.target.matches('.custom-aria2-settings-container')) {
|
||||
return;
|
||||
}
|
||||
OC_msg.startSaving('#ncdownloader-message-banner');
|
||||
const target = this.getAttribute("data-rel");
|
||||
let inputData = helper.getData(target);
|
||||
const path = inputData.url || "/apps/ncdownloader/personal/save";
|
||||
let url = generateUrl(path);
|
||||
Http.getInstance(url).setData(inputData).setHandler(function (data) {
|
||||
OC_msg.finishedSuccess('#ncdownloader-message-banner', "OK");
|
||||
}).send();
|
||||
});
|
||||
eventHandler.add('click', '#custom-aria2-settings-container', "button.add-custom-aria2-settings", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let element = e.target;
|
||||
let selector = "#aria2-settings-key-1";
|
||||
let form = settingsForm.getInstance();
|
||||
let nodeList, key, value;
|
||||
nodeList = document.querySelectorAll("[id^='aria2-settings-key']")
|
||||
if (nodeList.length === 0) {
|
||||
key = "aria2-settings-key-1";
|
||||
value = "aria2-settings-value-1";
|
||||
} else {
|
||||
let index = nodeList.length + 1;
|
||||
key = "aria2-settings-key-" + index;
|
||||
value = "aria2-settings-value-" + index;
|
||||
selector = "[id^='aria2-settings-key']";
|
||||
}
|
||||
element.before(form.createCustomInput(key, value));
|
||||
//appended the latest one
|
||||
nodeList = document.querySelectorAll("[id^='aria2-settings-key']")
|
||||
try {
|
||||
autoComplete.getInstance({
|
||||
selector: (nodeList.length !== 0) ? nodeList : selector,
|
||||
minChars: 1,
|
||||
source: function (term, suggest) {
|
||||
term = term.toLowerCase();
|
||||
let suggestions = [], data = aria2Options;
|
||||
for (const item of data) {
|
||||
if (item.toLowerCase().indexOf(term, 0) !== -1) {
|
||||
suggestions.push(item);
|
||||
}
|
||||
}
|
||||
suggest(suggestions);
|
||||
}
|
||||
}).run();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
eventHandler.add("click", "#custom-aria2-settings-container", "button.save-custom-aria2-settings", function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
let data = helper.getData(this.getAttribute("data-rel"));
|
||||
let url = generateUrl(data.path);
|
||||
delete data.path;
|
||||
OC_msg.startSaving('.message-banner');
|
||||
helper.makePair(data);
|
||||
Http.getInstance(url).setData(data).setHandler(function (data) {
|
||||
OC_msg.finishedSuccess('.message-banner', "OK");
|
||||
}).send();
|
||||
})
|
||||
eventHandler.add('click', '.ncdownloader-personal-settings', 'button.icon-close', function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
this.parentNode.remove();
|
||||
})
|
||||
Http.getInstance(generateUrl("/apps/ncdownloader/personal/aria2/get")).setHandler(function (data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
let input = [];
|
||||
for (let key in data) {
|
||||
input.push({ name: key, value: data[key], id: key });
|
||||
}
|
||||
settingsForm.getInstance().render(input);
|
||||
}).send();
|
||||
});
|
||||
110
src/settingsForm.js
Normal file
110
src/settingsForm.js
Normal file
@@ -0,0 +1,110 @@
|
||||
class settingsForm {
|
||||
parent = "custom-aria2-settings-container";
|
||||
constructor() {
|
||||
|
||||
}
|
||||
static getInstance() {
|
||||
return new this();
|
||||
}
|
||||
create(parent, element) {
|
||||
let label = this._createLabel(element.name, element.id)
|
||||
let input = this._createInput(element);
|
||||
//let saveBtn = this._createSaveBtn(element.id);
|
||||
let cancelBtn = this._createCancelBtn("has-content");
|
||||
let container = this._createContainer(element.id);
|
||||
[label, input, cancelBtn].forEach(ele => {
|
||||
container.appendChild(ele);
|
||||
})
|
||||
let button;
|
||||
if (button = parent.querySelector('button.add-custom-aria2-settings')) {
|
||||
return parent.insertBefore(container, button);
|
||||
}
|
||||
return parent.appendChild(container);
|
||||
}
|
||||
|
||||
createCustomInput(keyId, valueId) {
|
||||
let div = this._createContainer(keyId + "-container")
|
||||
div.appendChild(this._createInput({ id: keyId }));
|
||||
div.appendChild(this._createInput({ id: valueId }));
|
||||
div.appendChild(this._createCancelBtn());
|
||||
return div;
|
||||
}
|
||||
|
||||
createInput(element) {
|
||||
let div = document.createElement("div");
|
||||
div.classList.add(this.parent);
|
||||
/* element.forEach(element => {
|
||||
let label = document.createElement('label');
|
||||
label.setAttribute("for", element.id);
|
||||
let text = document.createTextNode(element.name);
|
||||
label.appendChild(text);
|
||||
div.appendChild(label);
|
||||
// div.appendChild(this._createInput(element));
|
||||
});*/
|
||||
div.appendChild(this._createInput(element));
|
||||
let button = document.createElement("button");
|
||||
//button.setAttribute("type",'button')
|
||||
button.classList.add("icon-close");
|
||||
div.appendChild(button);
|
||||
button = document.createElement("input");
|
||||
button.setAttribute('type', 'button');
|
||||
button.setAttribute('value', 'save');
|
||||
button.setAttribute("data-rel", this.parent);
|
||||
div.appendChild(button);
|
||||
return div;
|
||||
|
||||
}
|
||||
_createContainer(id) {
|
||||
let div = document.createElement("div");
|
||||
div.classList.add(id);
|
||||
return div;
|
||||
}
|
||||
_createCancelBtn(className = '') {
|
||||
let button = document.createElement("button");
|
||||
if (className)
|
||||
button.classList.add(className);
|
||||
//button.setAttribute("type",'button')
|
||||
button.classList.add("icon-close");
|
||||
return button;
|
||||
}
|
||||
_createSaveBtn(id) {
|
||||
let button = document.createElement("input");
|
||||
button.setAttribute('type', 'button');
|
||||
button.setAttribute('value', 'save');
|
||||
button.setAttribute("data-rel", id + "-container");
|
||||
return button;
|
||||
}
|
||||
_createLabel(name, id) {
|
||||
name = name.replace('_', '-');
|
||||
let label = document.createElement("lable");
|
||||
label.setAttribute("for", id);
|
||||
let text = document.createTextNode(name);
|
||||
label.appendChild(text);
|
||||
return label;
|
||||
}
|
||||
_createInput(data) {
|
||||
let input = document.createElement('input');
|
||||
let type = data.type || "text";
|
||||
let placeholder = data.placeholder || '';
|
||||
let value = data.value || placeholder;
|
||||
input.setAttribute('type', type);
|
||||
input.setAttribute('id', data.id);
|
||||
input.setAttribute("name", data.name || data.id);
|
||||
if (type === 'text') {
|
||||
input.setAttribute('value', value);
|
||||
input.setAttribute('placeholder', value);
|
||||
}
|
||||
input.classList.add('form-input-' + type);
|
||||
return input;
|
||||
}
|
||||
|
||||
render(data) {
|
||||
let parent = document.getElementById(this.parent)
|
||||
for (const element of data) {
|
||||
this.create(parent, element)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default settingsForm
|
||||
42
src/updatePage.js
Normal file
42
src/updatePage.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import helper from './helper'
|
||||
import $ from 'jquery'
|
||||
import Http from './http'
|
||||
const basePath = "/apps/ncdownloader/status/";
|
||||
const tableContainer = ".table";
|
||||
export default {
|
||||
run: function () {
|
||||
|
||||
const eventHandler = (event, type) => {
|
||||
event.preventDefault();
|
||||
const path = basePath + type;
|
||||
let name = type + "-downloads";
|
||||
//avoid repeated click
|
||||
if ($(tableContainer).attr("type") === name && helper.enabledPolling) {
|
||||
return;
|
||||
}
|
||||
helper.enabledPolling = 1;
|
||||
$(tableContainer).removeClass().addClass("table " + name);
|
||||
$(tableContainer).attr("type", name);
|
||||
let delay = 15000;
|
||||
if (name === "active-downloads") {
|
||||
delay = 1500;
|
||||
}
|
||||
helper.loop(helper.refresh, delay, ...[path])
|
||||
};
|
||||
$(".waiting-downloads").on("click", event => eventHandler(event, 'waiting'));
|
||||
$(".complete-downloads").on("click", event => eventHandler(event, 'complete'));
|
||||
$(".active-downloads").on("click", event => eventHandler(event, 'active'));
|
||||
$(".fail-downloads").on("click", event => eventHandler(event, 'fail'));
|
||||
|
||||
helper.refresh(basePath + "waiting")
|
||||
helper.refresh(basePath + "complete")
|
||||
helper.refresh(basePath + "fail")
|
||||
|
||||
helper.loop(helper.refresh, 1000, basePath + "active");
|
||||
|
||||
helper.polling(function (url) {
|
||||
url = helper.generateUrl(url);
|
||||
Http.getInstance(url).setMethod('GET').send();
|
||||
}, 60000, "/apps/ncdownloader/update");
|
||||
}
|
||||
}
|
||||
32
stylelint.config.js
Executable file
32
stylelint.config.js
Executable file
@@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
extends: 'stylelint-config-recommended-scss',
|
||||
rules: {
|
||||
indentation: 'tab',
|
||||
'selector-type-no-unknown': null,
|
||||
'number-leading-zero': null,
|
||||
'rule-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
ignore: ['after-comment', 'inside-block'],
|
||||
}
|
||||
],
|
||||
'declaration-empty-line-before': [
|
||||
'never',
|
||||
{
|
||||
ignore: ['after-declaration'],
|
||||
},
|
||||
],
|
||||
'comment-empty-line-before': null,
|
||||
'selector-type-case': null,
|
||||
'selector-list-comma-newline-after': null,
|
||||
'no-descending-specificity': null,
|
||||
'string-quotes': 'single',
|
||||
'selector-pseudo-element-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoElements: ['v-deep'],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: ['stylelint-scss'],
|
||||
}
|
||||
10
templates/Content.php
Normal file
10
templates/Content.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
?>
|
||||
<div id="app-content">
|
||||
<!-- app-content-wrapper is optional, only use if app-content-list -->
|
||||
<div id="app-content-wrapper">
|
||||
<?php print_unescaped($this->inc('contentTable'));?>
|
||||
</div>
|
||||
<div id="ncdownloader-action-links-container"></div>
|
||||
</div>
|
||||
4
templates/Index.php
Normal file
4
templates/Index.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<div id="ncdownloader-content">
|
||||
<?php print_unescaped($this->inc('Navigation'));?>
|
||||
<?php print_unescaped($this->inc('Content'));?>
|
||||
</div>
|
||||
95
templates/Navigation.php
Normal file
95
templates/Navigation.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
$aria2_running = $_['aria2_running'];
|
||||
$youtube_installed = $_['youtube_installed'];
|
||||
$aria2_installed = $_['aria2_installed'];
|
||||
|
||||
?>
|
||||
<div id="app-navigation">
|
||||
<div class="app-navigation-new" id="new-download" data-inputbox="form-input-wrapper">
|
||||
<button type="button" class="icon-add">
|
||||
<?php print($l->t('Add Download'));?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-navigation-new" id="new-download-ytdl" data-inputbox="form-input-wrapper">
|
||||
<button type="button" class="icon-add <?php $youtube_installed ? print "installed" : print "notinstalled";?>">
|
||||
<?php $youtube_installed ? print($l->t('YTDL Download')) : print $l->t('Youtube-dl NOT installed!');?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-navigation-new" id="torrent-search-button" data-inputbox="form-input-wrapper">
|
||||
<button type="button" class="icon-search">
|
||||
<?php print $l->t('Search torrents');?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-navigation-new" id="start-aria2">
|
||||
<?php if ($aria2_installed): ?>
|
||||
<button type="button" class="icon-power"
|
||||
data-aria2="<?php $aria2_running ? print $l->t('on') : print $l->t('off');?>">
|
||||
<?php $aria2_running ? print $l->t('Stop Aria2') : print $l->t('Start Aria2');?>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button type="button" class="icon-power notinstalled"
|
||||
data-aria2="<?php $aria2_running ? print $l->t('on') : print $l->t('off');?>">
|
||||
<?php print $l->t('Aria2 is not installed!');?>
|
||||
</button>
|
||||
<?php endif;?>
|
||||
</div>
|
||||
<div class="app-navigation-new" id="ncdownloader-user-settings">
|
||||
<button type="button" class="icon-settings" path="/settings/user/ncdownloader">
|
||||
<?php print $l->t('Settings');?>
|
||||
</button>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="active-downloads">
|
||||
<div class="app-navigation-entry-bullet"></div>
|
||||
<a href="/apps/ncdownloader/dl/active">
|
||||
<?php print($l->t('Active Downloads'));?>
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter" id="active-downloads-counter">
|
||||
<div class="number">0</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="waiting-downloads">
|
||||
<div class="app-navigation-entry-bullet"></div>
|
||||
<a href="/apps/ncdownloader/dl/waiting">
|
||||
<?php print($l->t('Waiting Downloads'));?>
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter" id="waiting-downloads-counter">
|
||||
<div class="number">0</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="complete-downloads">
|
||||
<div class="app-navigation-entry-bullet"></div>
|
||||
<a href="/apps/ncdownloader/dl/complete">
|
||||
<?php print($l->t('Complete Downloads'));?>
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter" id="complete-downloads-counter">
|
||||
<div class="number">0</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="fail-downloads">
|
||||
<div class="app-navigation-entry-bullet"></div>
|
||||
<a href="/apps/ncdownloader/dl/fail">
|
||||
<?php print($l->t('Failed Downloads'));?>
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter" id="fail-downloads-counter">
|
||||
<div class="number">0</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
9
templates/contentTable.php
Normal file
9
templates/contentTable.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
?>
|
||||
<div class="ncdownloader-content-wrapper">
|
||||
<div id="ncdownloader-form-wrapper"> </div>
|
||||
<table id="ncdownloader-table-content" type="active-downloads">
|
||||
</table>
|
||||
<div id="ncdownloader-table-wrapper" class="table" type="active-downloads"></div>
|
||||
</div>
|
||||
33
templates/settings/Admin.php
Normal file
33
templates/settings/Admin.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud - ocDownloader
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the LICENSE file.
|
||||
*
|
||||
* @author Xavier Beurois <www.sgc-univ.net>
|
||||
* @copyright Xavier Beurois 2015
|
||||
*/
|
||||
//script("ncdownloader", 'common');
|
||||
//script("ncdownloader", 'settings/admin');
|
||||
script("ncdownloader", 'appSettings');
|
||||
?>
|
||||
<div class="ncdownloader-admin-settings">
|
||||
<form id="ncdownloader" class="section">
|
||||
<h2>ncDownloader admin Settings</h2>
|
||||
<div><span class="info">
|
||||
<?php print($l->t('Leave empty to reset a setting value'));?>
|
||||
</span>
|
||||
<span id="ncdownloader-message"></span>
|
||||
</div>
|
||||
<div id="ncd_rpctoken_settings" path="<?php print $path;?>">
|
||||
<label for="ncd_rpctoken">
|
||||
<?php print($l->t('Aria2 RPC Token'));?>
|
||||
</label>
|
||||
<input type="text" class="ncd_rpctoken" id="ncd_rpctoken" name="ncd_rpctoken"
|
||||
value="<?php print($ncd_rpctoken ?? 'ncdownloader123');?>"
|
||||
placeholder="ncdownloader123" />
|
||||
<input type="button" value="<?php print($l->t('Save'));?>" data-rel="ncd_rpctoken_settings" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
76
templates/settings/Personal.php
Normal file
76
templates/settings/Personal.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
//script("ncdownloader", 'common');
|
||||
//script("ncdownloader", 'settings/personal');
|
||||
script("ncdownloader", 'appSettings');
|
||||
style("ncdownloader", "autocomplete");
|
||||
style("ncdownloader", "style");
|
||||
extract($_);
|
||||
$time_map = array('i' => 'minutes', 'h' => 'hours', 'w' => 'weeks', 'd' => 'days', 'y' => 'years');
|
||||
?>
|
||||
<div class="ncdownloader-personal-settings">
|
||||
<div id="ncdownloader-settings-form" class="section">
|
||||
<div class="ncdownloader-common-settings">
|
||||
<h2>NCdownloader Settings</h2>
|
||||
<div id="ncdownloader-message-banner"></div>
|
||||
<div><span class="info">
|
||||
<?php print($l->t('Leave empty to reset a setting value'));?>
|
||||
</span>
|
||||
</div>
|
||||
<div id="ncd_downloader_dir_settings" path="<?php print $path;?>">
|
||||
<label for="ncd_downloader_dir">
|
||||
<?php print($l->t('Downloads Folder'));?>
|
||||
</label>
|
||||
<input type="text" class="ncd_downloader_dir" id="ncd_downloader_dir" name="ncd_downloader_dir"
|
||||
value="<?php print($ncd_downloader_dir ?? '/Downloads');?>" placeholder="/Downloads" />
|
||||
<input type="button" value="<?php print($l->t('Save'));?>" data-rel="ncd_downloader_dir_settings" />
|
||||
</div>
|
||||
<div id="ncd_torrents_dir_settings" path="<?php print $path;?>">
|
||||
<label for="ncd_torrents_dir">
|
||||
<?php print($l->t('Torrents Folder'));?>
|
||||
</label>
|
||||
<input type="text" class="ncd_torrents_dir" id="ncd_torrents_dir"
|
||||
value="<?php print($ncd_torrents_dir ?? '/Downloads/Files/Torrents');?>"
|
||||
placeholder="/Downloads/Files/Torrents" />
|
||||
<input type="button" value="<?php print($l->t('Save'));?>" data-rel="ncd_torrents_dir_settings" />
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="ncdownloader-bt-settings">
|
||||
<h2>
|
||||
<?php print($l->t('BitTorrent protocol settings - Ratio'));?>
|
||||
</h2>
|
||||
<div id="ncd_btratio_container" path="<?php print $path;?>">
|
||||
<label for="ncd_seed_ratio">
|
||||
<?php print($l->t('Seed ratio'));?>
|
||||
</label>
|
||||
<input id="ncd_seed_ratio" value="<?php print($ncd_seed_ratio ?? 1.0);?>" placeholder="1.0">
|
||||
</input>
|
||||
<input type="button" value="<?php print($l->t('Save'));?>" data-rel="ncd_btratio_container" />
|
||||
</div>
|
||||
<div>
|
||||
<div id="seed_time_settings_container" path="<?php print $path;?>">
|
||||
<label for="ncd_seed_time">
|
||||
<?php print($l->t('Seed Time in minutes'));?>
|
||||
</label>
|
||||
<input id="ncd_seed_time" type="text" class="ncd_seed_time"
|
||||
value="<?php print($ncd_seed_time ?? 1);?>" placeholder="1 m,h,d,w,m">
|
||||
</input>
|
||||
<input type="button" value="<?php print($l->t('Save'));?>"
|
||||
data-rel="seed_time_settings_container" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>
|
||||
<?php print($l->t('Custom Aria2 Settings'));?>
|
||||
</h2>
|
||||
<div class="message-banner"></div>
|
||||
<div classs="section" id="custom-aria2-settings-container" path="/apps/ncdownloader/personal/aria2/save">
|
||||
<button class="add-custom-aria2-settings">
|
||||
<?php print $l->t('Add Options');?>
|
||||
</button>
|
||||
<button class="save-custom-aria2-settings" data-rel="custom-aria2-settings-container">
|
||||
<?php print $l->t('Save');?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
70
webpack.app.js
Normal file
70
webpack.app.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/index.js',
|
||||
appSettings: './src/settings.js'
|
||||
},
|
||||
devtool: "source-map",
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'js'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.s?[ac]ss$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
"style-loader",
|
||||
// Translates CSS into CommonJS
|
||||
"css-loader",
|
||||
// Compiles Sass to CSS
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'svg-url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
/* resolve: {
|
||||
alias: {
|
||||
vue: 'vue/dist/vue.js'
|
||||
},
|
||||
}
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.vue$/, use: 'vue-loader' },
|
||||
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader']},
|
||||
]
|
||||
},*/
|
||||
plugins: [
|
||||
// new VueLoaderPlugin(),
|
||||
new webpack.ProvidePlugin({
|
||||
$: "jquery",
|
||||
jquery: "jQuery",
|
||||
"window.jQuery": "jquery"
|
||||
}),
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user