From d1541f9907e48a0b52a92b7690fbeca3f73ca578 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 May 2022 15:09:56 -0700 Subject: [PATCH 1/4] bump version 0.3.3 --- .editorconfig | 41 - .prospector.yml | 2 + .pylintrc | 433 ----------- MANIFEST.in | 3 +- assets/__init__.py | 0 coverage.xml | 733 +++++++++--------- package.json | 4 +- pyproject.toml | 40 + requirements.txt | 2 +- setup.cfg | 1 + tests/test_checktab.py | 32 + torrentfileQt/__init__.py | 13 +- .../assets}/archive.png | Bin .../assets}/arrow-down.png | Bin .../assets}/arrow-downcopy.png | Bin .../icons => torrentfileQt/assets}/file.png | Bin .../icons => torrentfileQt/assets}/folder.png | Bin .../icons => torrentfileQt/assets}/hash.png | Bin .../icons => torrentfileQt/assets}/music.png | Bin .../icons => torrentfileQt/assets}/plus.png | Bin .../icons => torrentfileQt/assets}/scale.png | Bin torrentfileQt/assets/torrentfile.ico | Bin 0 -> 67244 bytes torrentfileQt/assets/torrentfile.png | Bin 0 -> 31493 bytes .../icons => torrentfileQt/assets}/video.png | Bin torrentfileQt/checkTab.py | 58 +- torrentfileQt/createTab.py | 23 +- torrentfileQt/editorTab.py | 27 +- torrentfileQt/infoTab.py | 27 +- torrentfileQt/magnetTab.py | 2 +- torrentfileQt/qss.py | 4 +- torrentfileQt/version.py | 10 +- torrentfileQt/window.py | 5 +- 32 files changed, 574 insertions(+), 886 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .pylintrc delete mode 100644 assets/__init__.py rename {assets/icons => torrentfileQt/assets}/archive.png (100%) rename {assets/icons => torrentfileQt/assets}/arrow-down.png (100%) rename {assets/icons => torrentfileQt/assets}/arrow-downcopy.png (100%) rename {assets/icons => torrentfileQt/assets}/file.png (100%) rename {assets/icons => torrentfileQt/assets}/folder.png (100%) rename {assets/icons => torrentfileQt/assets}/hash.png (100%) rename {assets/icons => torrentfileQt/assets}/music.png (100%) rename {assets/icons => torrentfileQt/assets}/plus.png (100%) rename {assets/icons => torrentfileQt/assets}/scale.png (100%) create mode 100644 torrentfileQt/assets/torrentfile.ico create mode 100644 torrentfileQt/assets/torrentfile.png rename {assets/icons => torrentfileQt/assets}/video.png (100%) diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 0af92f3..0000000 --- a/.editorconfig +++ /dev/null @@ -1,41 +0,0 @@ -root = true - -[*] -end_of_line = lf -insert_final_newline = true -charset = utf-8 - -[*.md] -trim_trailing_whitespace = false -insert_final_newline = true - - -[*.{py,c,cpp,java}] -indent_style = space -indent_size = 4 - -[*.py] -trim_trailing_whitespace = true -indent_style = space - -[*.{js,ts,json}] -indent_style = space -indent_size = 2 - -[lib/**.js] -indent_style = space -indent_size = 2 - -[Makefile] -indent_style = tab - -[{package.json,.travis.yml}] -indent_style = space -indent_size = 2 - -[*.bat] -indent_style = tab -end_of_line = crlf - -[LICENSE] -insert_final_newline = false diff --git a/.prospector.yml b/.prospector.yml index 1bc1047..43862bb 100644 --- a/.prospector.yml +++ b/.prospector.yml @@ -35,6 +35,8 @@ pep8: pylint: run: true + options: + config: pyproject.toml disable: - R1729 diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index d42281f..0000000 --- a/.pylintrc +++ /dev/null @@ -1,433 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -# jobs=1 -jobs=2 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=redefined-outer-name,unspecified-encoding,protected-access,attribute-defined-outside-init,consider-using-with,use-a-generator,not-callable,useless-super-delegation,invalid-name,no-name-in-module,redefined-builtin, subprocess-run-check,no-member,consider-using-f-string - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -msg-template='{path} {line}: {msg} ({symbol})' - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules= - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines= - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=80 - -# Maximum number of lines in a module -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=no - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[BASIC] - -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class names -# class-name-hint=[A-Z_][a-zA-Z0-9]+$ -class-name-hint=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=l,r,g,b,w,i,j,k,v,n,x,y,l,z,ex,ok,fd,d,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=yes - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*)|([A-Z0-9]{2,30}))$ - -# Regular expression matching correct variable names -variable-rgx=(([a-zA-Z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=15 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=35 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=28 - -# Maximum number of locals for function / method body -max-locals=20 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=120 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=0 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/MANIFEST.in b/MANIFEST.in index de03dc1..3f238d6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,8 +4,7 @@ include Makefile include package.json include requirements.txt -graft assets -graft tests +graft torrentfileQt/assets graft torrentfileQt global-exclude *.py[cod] diff --git a/assets/__init__.py b/assets/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/coverage.xml b/coverage.xml index 250aa2e..f180339 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,5 +1,5 @@ - + @@ -126,6 +126,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -760,47 +789,37 @@ - - - - - - - - - - + + - + + + - - - - - + @@ -811,235 +830,254 @@ - + + + + + + + + + - - - - - - - - + + - - - + + + + + + + + - - + - - - + + + + + - - - + + - - - - - - - - - - + + + + + - - - - - - + + + + + + + + - - - + + - + + - - - + + - + + - + - - - - - + + - + + + + + - - - - + - + + + + + + + + - - + + - - - - - - - - - - - + + + + + + + - - + - - - - - + - + + + + + + - + + + - + - - - + + - + + - + - - - - + + + + + + + + + + + + + + + + @@ -1051,39 +1089,29 @@ - - - - - - - - - + + + + - - - - - + - + - + - @@ -1092,21 +1120,21 @@ + + - - - + @@ -1117,8 +1145,8 @@ - + @@ -1129,7 +1157,6 @@ - @@ -1154,132 +1181,143 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + - + - - + - + - + + + + - + - - - - - + + + + + + + + - - - - - - - + + + + + + - - - - - - + + - - - + + - + + + - - - + + + + + + - - - - - - + + - - + + - + + - - - + - + + + + + + + + + @@ -1292,18 +1330,9 @@ - - - + - - - - - - - @@ -1322,7 +1351,9 @@ + + @@ -1343,22 +1374,20 @@ - + + + + - - - - - @@ -1366,6 +1395,7 @@ + @@ -1375,26 +1405,27 @@ + + + + - + - - - - + + + + + + - - - + - - - @@ -1402,79 +1433,79 @@ - + + + + - - - + + + - - + - - - - + + - + + + - - - - + + + - - - - - - - + + + + + + + + + + - - - - + - @@ -1486,9 +1517,7 @@ - - @@ -1496,13 +1525,22 @@ + + + - + + + + + + + @@ -1518,34 +1556,27 @@ - - - + + - - - - - - + + - - + + - - @@ -1555,41 +1586,41 @@ + + - - + + + + - - + - - + - + + + - - + - - + - - @@ -1664,94 +1695,97 @@ + + - - - - - + + + + + - - + + - - + + - - + - + + + + + - - - - - + + + - - - - - - - + + + + + + + - - - - - + + + + + + - - + - - + + + - + - - - - + + - - + + + - - + + - - + + @@ -1762,57 +1796,61 @@ - + - - + + - + - + - + - - - + + + - - - + + + - - + + + + + + @@ -2064,13 +2102,12 @@ - - - - + + + @@ -2083,17 +2120,18 @@ + - - - - - + + + + + @@ -2105,21 +2143,21 @@ - - + + - - + + - - - + + + @@ -2136,17 +2174,16 @@ - - - + + + - - + + - - + diff --git a/package.json b/package.json index 750143a..042c881 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "torrentfileqt", "displayName": "TorrentfileQt", - "version": "0.3.3", + "version": "0.3.4", "description": "GUI torrentfile creator.", "repository": { "type": "git", @@ -11,7 +11,7 @@ "author": "alexpdev", "url": "https://github.com/alexpdev/torrentfileQt", "email": "alexpdev@protonmail.com", - "license": "Apache", + "license": "Apache 2.0", "bugs": { "url": "https://github.com/alexpdev/TorrentfileQt/issues" }, diff --git a/pyproject.toml b/pyproject.toml index 5602234..691357d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,3 +21,43 @@ testpaths = [ ] console_output_style = "progress" addopts = "--maxfail=5" + +[tool.pylint.messages_control] +disable= [ + 'redefined-outer-name', + 'unspecified-encoding', + 'protected-access', + 'attribute-defined-outside-init', + 'consider-using-with', + 'use-a-generator', + 'not-callable', + 'useless-super-delegation', + 'invalid-name', + 'no-name-in-module', + 'redefined-builtin', + 'subprocess-run-check', + 'no-member', + 'consider-using-f-string' +] + +[tool.pylint.format] +max-line-length=80 +max-module-lines=1000 +no-space-check=['trailing-comma','dict-separator'] +single-line-if-stmt='yes' + +[tool.pylint.basic] +good-names=[ + 'l','r','g','b','w','i','j','k','v','n','x', + 'y','l','z','ex','ok','fd','d','Run','_' +] + +[tool.pylint.design] +max-args=15 +max-attributes=35 +max-branches=28 +max-locals=20 +max-parents=7 +max-public-methods=20 +max-returns=6 +max-statements=120 diff --git a/requirements.txt b/requirements.txt index e13b980..cc6b56b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ wheel setuptools -torrentfile==0.7.6 +torrentfile==0.7.9 PySide6 pyben diff --git a/setup.cfg b/setup.cfg index ac4f1a0..7315e62 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,3 +19,4 @@ install_requires = torrentfile PySide6 pyben +include_package_data=True diff --git a/tests/test_checktab.py b/tests/test_checktab.py index c3a8488..8505ec3 100644 --- a/tests/test_checktab.py +++ b/tests/test_checktab.py @@ -167,3 +167,35 @@ def test_singlefile(size, ext, index, version, wind): widges = checktab.treeWidget.itemWidgets assert all(i.total == i.value for i in widges.values()) rmpath(tfile, metafile) + + +@pytest.mark.parametrize("version", [1, 2, 3]) +def test_singlefile_large(version, wind): + """Test the singlefile with large size for create and check tabs.""" + window, _ = wind + createtab = window.central.createWidget + checktab = window.central.checkWidget + window.central.setCurrentWidget(checktab) + testfile = str(tempfile(exp=28)) + tfile = testfile + '.dat' + os.rename(testfile, tfile) + metafile = tfile + ".torrent" + createtab.path_input.clear() + createtab.output_input.clear() + createtab.browse_file_button.browse(tfile) + createtab.output_input.setText(metafile) + btns = [createtab.v1button, createtab.v2button, createtab.hybridbutton] + for i, btn in enumerate(btns): + if i + 1 == version: + btn.click() + break + createtab.submit_button.click() + createtab.submit_button.join() + checktab.fileInput.clear() + checktab.searchInput.clear() + checktab.fileInput.setText(metafile) + checktab.searchInput.setText(tfile) + checktab.checkButton.click() + widges = checktab.treeWidget.itemWidgets + assert all(i.total == i.value for i in widges.values()) + rmpath(tfile, metafile) diff --git a/torrentfileQt/__init__.py b/torrentfileQt/__init__.py index ee7e856..7fa2be6 100644 --- a/torrentfileQt/__init__.py +++ b/torrentfileQt/__init__.py @@ -25,9 +25,16 @@ from torrentfileQt.window import Application, Window, alt_start, start -myappid = f'TorrentfileQt.{__version__}' -if sys.platform == 'win32': +myappid = f"TorrentfileQt.{__version__}" +if sys.platform == "win32": ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) __author__ = "alexpdev" -__all__ = ["Application", "Window", "alt_start", "start", "__version__", "ASSETS"] +__all__ = [ + "Application", + "Window", + "alt_start", + "start", + "__version__", + "ASSETS", +] diff --git a/assets/icons/archive.png b/torrentfileQt/assets/archive.png similarity index 100% rename from assets/icons/archive.png rename to torrentfileQt/assets/archive.png diff --git a/assets/icons/arrow-down.png b/torrentfileQt/assets/arrow-down.png similarity index 100% rename from assets/icons/arrow-down.png rename to torrentfileQt/assets/arrow-down.png diff --git a/assets/icons/arrow-downcopy.png b/torrentfileQt/assets/arrow-downcopy.png similarity index 100% rename from assets/icons/arrow-downcopy.png rename to torrentfileQt/assets/arrow-downcopy.png diff --git a/assets/icons/file.png b/torrentfileQt/assets/file.png similarity index 100% rename from assets/icons/file.png rename to torrentfileQt/assets/file.png diff --git a/assets/icons/folder.png b/torrentfileQt/assets/folder.png similarity index 100% rename from assets/icons/folder.png rename to torrentfileQt/assets/folder.png diff --git a/assets/icons/hash.png b/torrentfileQt/assets/hash.png similarity index 100% rename from assets/icons/hash.png rename to torrentfileQt/assets/hash.png diff --git a/assets/icons/music.png b/torrentfileQt/assets/music.png similarity index 100% rename from assets/icons/music.png rename to torrentfileQt/assets/music.png diff --git a/assets/icons/plus.png b/torrentfileQt/assets/plus.png similarity index 100% rename from assets/icons/plus.png rename to torrentfileQt/assets/plus.png diff --git a/assets/icons/scale.png b/torrentfileQt/assets/scale.png similarity index 100% rename from assets/icons/scale.png rename to torrentfileQt/assets/scale.png diff --git a/torrentfileQt/assets/torrentfile.ico b/torrentfileQt/assets/torrentfile.ico new file mode 100644 index 0000000000000000000000000000000000000000..51c322b96e9fb754e7d29cfe8c7bd6a402004210 GIT binary patch literal 67244 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 10_000_000: + val = val // (2**20) + elif self.total > 10_000: + val = val // (2**10) + return val + def addValue(self, value): """Increase value of progressbar.""" currentvalue = self.value() - addedVal = currentvalue + value + out = self.normalize(value) + addedVal = currentvalue + out self.setValue(addedVal) @@ -326,7 +347,7 @@ class TreeWidget(QTreeWidget): parent(`QWidget`, default=None) """ - addPathChild = Signal([str, int]) + addPathChild = Signal([str, str]) reChecking = Signal([str, str]) addValue = Signal([str, int]) addCount = Signal([str, int]) @@ -381,6 +402,7 @@ def clear(self): def add_path_child(self, path, size): """Add branch to tree.""" path = Path(path) + size = int(size) partials = path.parts item, item_tree = None, self.item_tree for i, partial in enumerate(partials): @@ -394,27 +416,23 @@ def add_path_child(self, path, size): item_tree[partial] = {"widget": item} if i == len(partials) - 1: if path.suffix in [".avi", ".mp4", ".mkv", ".mov"]: - fileicon = QIcon(os.path.join(ASSETS, "icons", "video.png")) + fileicon = QIcon(os.path.join(ASSETS, "video.png")) elif path.suffix in [".rar", ".zip", ".7z", ".tar", ".gz"]: - fileicon = QIcon( - os.path.join(ASSETS, "icons", "archive.png") - ) + fileicon = QIcon(os.path.join(ASSETS, "archive.png")) elif re.match(r"\.r\d+$", path.suffix): - fileicon = QIcon( - os.path.join(ASSETS, "icons", "archive.png") - ) + fileicon = QIcon(os.path.join(ASSETS, "archive.png")) elif path.suffix in [".mp3", ".wav", ".flac", ".m4a"]: - fileicon = QIcon(os.path.join(ASSETS, "icons", "music.png")) + fileicon = QIcon(os.path.join(ASSETS, "music.png")) else: - fileicon = QIcon(os.path.join(ASSETS, "icons", "file.png")) + fileicon = QIcon(os.path.join(ASSETS, "file.png")) progressBar = ProgressBar(parent=None, size=size) - self.setItemWidget(item, 2, progressBar) + self.setItemWidget(item, 1, progressBar) item.progbar = progressBar self.itemWidgets[str(path)] = item else: - fileicon = QIcon(os.path.join(ASSETS, "icons", "folder.png")) + fileicon = QIcon(os.path.join(ASSETS, "folder.png")) item.setIcon(0, fileicon) - item.setText(1, partial) + item.setText(0, partial) item_tree = item_tree[partial] self.window.app.processEvents() self.paths.append(path) @@ -442,7 +460,7 @@ def addTreeWidgets(self): else: relpath = os.path.relpath(val["path"], self.root) length = val["length"] - self.tree.addPathChild.emit(relpath, length) + self.tree.addPathChild.emit(relpath, str(length)) def iter_hashes(self): """Iterate through hashes and compare to torrentfile hashes.""" diff --git a/torrentfileQt/createTab.py b/torrentfileQt/createTab.py index 52dbb2c..ff6aca3 100644 --- a/torrentfileQt/createTab.py +++ b/torrentfileQt/createTab.py @@ -27,10 +27,21 @@ from threading import Thread from PySide6.QtCore import Qt -from PySide6.QtWidgets import (QCheckBox, QComboBox, QFileDialog, QGridLayout, - QHBoxLayout, QLabel, QLineEdit, QPlainTextEdit, - QPushButton, QRadioButton, QSpacerItem, - QToolButton, QWidget) +from PySide6.QtWidgets import ( + QCheckBox, + QComboBox, + QFileDialog, + QGridLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QPlainTextEdit, + QPushButton, + QRadioButton, + QSpacerItem, + QToolButton, + QWidget, +) from torrentfile.torrent import TorrentFile, TorrentFileHybrid, TorrentFileV2 from torrentfile.utils import path_piece_length @@ -229,9 +240,9 @@ def submit(self): args["announce"] = announce url_list = self.widget.web_seed_input.toPlainText() - url_list = [i for i in url_list.split('\n') if i] + url_list = [i for i in url_list.split("\n") if i] if url_list: - args['url_list'] = url_list + args["url_list"] = url_list # Calculates piece length if not specified by user. outtext = os.path.realpath(self.widget.output_input.text()) diff --git a/torrentfileQt/editorTab.py b/torrentfileQt/editorTab.py index eb5e8d4..ddb2534 100644 --- a/torrentfileQt/editorTab.py +++ b/torrentfileQt/editorTab.py @@ -24,10 +24,19 @@ import pyben from PySide6.QtCore import Qt, Signal -from PySide6.QtWidgets import (QComboBox, QFileDialog, QHBoxLayout, QLabel, - QLineEdit, QPushButton, QTableWidget, - QTableWidgetItem, QToolButton, QVBoxLayout, - QWidget) +from PySide6.QtWidgets import ( + QComboBox, + QFileDialog, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QTableWidget, + QTableWidgetItem, + QToolButton, + QVBoxLayout, + QWidget, +) from torrentfileQt.qss import table_styles @@ -87,7 +96,7 @@ def dragMoveEvent(self, event): self.counter += 1 return event.ignore() - def dropEvent(self, event): + def dropEvent(self, event) -> bool: """Drag drop event for widgit.""" urls = event.mimeData().urls() path = urls[0].toLocalFile() @@ -101,7 +110,7 @@ def dropEvent(self, event): class Button(QPushButton): """Button Widget for saving results to .torrent file.""" - def __init__(self, text, parent=None): + def __init__(self, text: str, parent=None): """Constructor for the save button on torrent editor tab.""" super().__init__(text, parent=parent) self.widget = parent @@ -149,7 +158,7 @@ def __init__(self, parent=None): self.window = parent.window self.clicked.connect(self.browse) - def browse(self, path=None): + def browse(self, path: str = None): """Browse method for finding the .torrent file user wishes to edit.""" if not path: # pragma: no coverage path, _ = QFileDialog.getOpenFileName( @@ -169,7 +178,7 @@ class AddItemButton(QToolButton): def __init__(self, parent): """Construct the Button.""" super().__init__(parent) - self.setStyleSheet(table_styles['button']) + self.setStyleSheet(table_styles["button"]) self.parent = parent self.setText("add") self.box = None @@ -192,7 +201,7 @@ class RemoveItemButton(QToolButton): def __init__(self, parent): """Construct the Button.""" super().__init__(parent) - self.setStyleSheet(table_styles['button']) + self.setStyleSheet(table_styles["button"]) self.parent = parent self.setText("remove") self.box = None diff --git a/torrentfileQt/infoTab.py b/torrentfileQt/infoTab.py index b708508..609aad8 100644 --- a/torrentfileQt/infoTab.py +++ b/torrentfileQt/infoTab.py @@ -27,9 +27,16 @@ import pyben from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QIcon -from PySide6.QtWidgets import (QFileDialog, QGridLayout, QLabel, QLineEdit, - QPushButton, QTreeWidget, QTreeWidgetItem, - QWidget) +from PySide6.QtWidgets import ( + QFileDialog, + QGridLayout, + QLabel, + QLineEdit, + QPushButton, + QTreeWidget, + QTreeWidgetItem, + QWidget, +) from torrentfileQt.qss import infoLineEdit, infoLineEditLight @@ -76,18 +83,18 @@ def apply_value(self, text, length): if i + 1 == len(partials): _, suffix = os.path.splitext(partial) if suffix in [".mp4", ".mkv"]: - iconpath = os.path.join(ASSETS, "icons", "video.png") + iconpath = os.path.join(ASSETS, "video.png") elif suffix in [".rar", ".zip", ".7z", ".tar", ".gz"]: - iconpath = os.path.join(ASSETS, "icons", "archive.png") + iconpath = os.path.join(ASSETS, "archive.png") elif re.match(r"\.r\d+", suffix): - iconpath = os.path.join(ASSETS, "icons", "archive.png") + iconpath = os.path.join(ASSETS, "archive.png") elif suffix in [".wav", ".mp3", ".flac", ".m4a", ".aac"]: - iconpath = os.path.join(ASSETS, "icons", "music.png") + iconpath = os.path.join(ASSETS, "music.png") else: - iconpath = os.path.join(ASSETS, "icons", "file.png") + iconpath = os.path.join(ASSETS, "file.png") item.setLength(length) else: - iconpath = os.path.join(ASSETS, "icons", "folder.png") + iconpath = os.path.join(ASSETS, "folder.png") icon = QIcon(iconpath) item.setIcon(0, icon) item.setText(1, partial) @@ -109,7 +116,7 @@ def __init__(self, group): def setLength(self, length): """Set length leaf for tree branches.""" child = TreeItem(0) - icon = QIcon(os.path.join(ASSETS, "icons", "scale.png")) + icon = QIcon(os.path.join(ASSETS, "scale.png")) child.setIcon(0, icon) child.setText(1, f"Size: {length} (bytes)") self.setExpanded(True) diff --git a/torrentfileQt/magnetTab.py b/torrentfileQt/magnetTab.py index 9cb174b..6cc2626 100644 --- a/torrentfileQt/magnetTab.py +++ b/torrentfileQt/magnetTab.py @@ -32,7 +32,7 @@ class MagnetWidget(QWidget): """Tab for creating magnet URL's and downloading torrentfiles from them.""" def __init__(self, parent=None): - """Initialize the widget form for creating magnet URI's from a metafile.""" + """Initialize the widget for creating magnet URI's from a metafile.""" super().__init__(parent=parent) self.window = parent.window self.setStyleSheet(""" diff --git a/torrentfileQt/qss.py b/torrentfileQt/qss.py index 03fc3de..4e71ae4 100644 --- a/torrentfileQt/qss.py +++ b/torrentfileQt/qss.py @@ -21,7 +21,7 @@ import os from urllib.request import pathname2url as path2url -arrow = os.path.join(os.environ['ASSETS'], 'icons', 'arrow-down.png') +arrow = os.path.join(os.environ["ASSETS"], "arrow-down.png") dark_theme = """ @@ -763,5 +763,5 @@ padding: 2px; border-radius: 12px; } - """ + """, } diff --git a/torrentfileQt/version.py b/torrentfileQt/version.py index a50cef1..bb29321 100644 --- a/torrentfileQt/version.py +++ b/torrentfileQt/version.py @@ -18,16 +18,16 @@ import os +__version__ = "0.3.4" + + def _conf(): """Create some enviornment variables.""" parent = os.path.dirname(__file__) - top = os.path.dirname(parent) - assets = os.path.join(top, 'assets') + assets = os.path.join(parent, "assets") path = os.path.relpath(assets, ".") return path ASSETS = str(_conf()) -os.environ['ASSETS'] = ASSETS - -__version__ = "0.3.3" +os.environ["ASSETS"] = ASSETS diff --git a/torrentfileQt/window.py b/torrentfileQt/window.py index b3b5349..b48f52d 100644 --- a/torrentfileQt/window.py +++ b/torrentfileQt/window.py @@ -23,8 +23,7 @@ from PySide6.QtCore import Signal from PySide6.QtGui import QIcon -from PySide6.QtWidgets import (QApplication, QMainWindow, QTabWidget, - QVBoxLayout) +from PySide6.QtWidgets import QApplication, QMainWindow, QTabWidget, QVBoxLayout from torrentfileQt.checkTab import CheckWidget from torrentfileQt.createTab import CreateWidget @@ -58,7 +57,7 @@ def __init__(self, parent=None, app=None): super().__init__(parent=parent) self.app = app self.menubar = MenuBar(parent=self) - self.icon = QIcon(os.path.join(ASSETS, "favicon.png")) + self.icon = QIcon(os.path.join(ASSETS, "torrentfile.png")) self.setObjectName("Mainwindow") self.setWindowTitle("TorrentfileQt") self.setWindowIcon(self.icon) From 94e8de5e7ae53e8129470b7018a917f4a52fb7dc Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 May 2022 18:54:29 -0700 Subject: [PATCH 2/4] updates to widgets --- .gitignore | 2 +- Makefile | 54 ++---------- README.md | 28 ++++++ coverage.xml | 174 ++++++++++++++++++++----------------- requirements.txt | 3 - tests/test_edittab.py | 2 +- torrentfileQt/checkTab.py | 7 +- torrentfileQt/createTab.py | 8 +- torrentfileQt/editorTab.py | 15 +++- 9 files changed, 152 insertions(+), 141 deletions(-) diff --git a/.gitignore b/.gitignore index 2301629..6e01e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ temp/ temp/ testdir/ main.py - +.runner/ # Distribution / packaging .vscode/ TESTINGDIR/ diff --git a/Makefile b/Makefile index e231b41..91172da 100644 --- a/Makefile +++ b/Makefile @@ -21,35 +21,6 @@ for line in sys.stdin: endef export PRINT_HELP_PYSCRIPT -define FIXES -import os -from pathlib import Path -from torrentfileQt.version import __version__ - -distpath = Path(os.getcwd()).resolve() / "dist" -for item in distpath.iterdir(): - if item.name == "torrentfileQt.exe": - os.rename(item, distpath / f"torrentfileQt-v{__version__}.exe") - elif item.name == "torrentfileQt.zip": - os.rename(item, distpath / f"torrentfileQt-v{__version__}-Winx64.zip") -endef -export FIXES - -define UNIXES -import os -from pathlib import Path -from torrentfileQt.version import __version__ - -distpath = Path(os.getcwd()).resolve() / "dist" -for item in distpath.iterdir(): - if item.name == "torrentfileQt.exe": - os.rename(item, distpath / f"torrentfileQt-v{__version__}-linux") - elif item.name == "torrentfileQt.zip": - os.rename(item, distpath / f"torrentfileQt-v{__version__}-linux.zip") -endef -export FIXES - - BROWSER := python -c "$$BROWSER_PYSCRIPT" help: @@ -79,7 +50,7 @@ lint: ## run linters on codebase prospector tests test: ## run tests quickly with the default Python - pip install --upgrade --force-reinstall --no-cache -rrequirements.txt + pip install --upgrade --force-reinstall --no-cache torrentfile pyben pytest tests --cov=torrentfileQt --cov=tests coverage report coverage xml -o coverage.xml @@ -89,26 +60,19 @@ push: clean lint test ## push changes to remote git commit -m "$m" git push +start: ## start program + torrentfileQt + release: clean test lint ## release to pypi python setup.py sdist bdist_wheel bdist_egg twine upload dist/* build: clean + python -m pip install --upgrade --no-cache --force-reinstall torrentfile pyben pip wheel setuptools python setup.py sdist bdist_wheel bdist_egg - rm -rfv ../runner - mkdir ../runner - touch ../runner/exe - cp ./torrentfileQt/assets/torrentfile.ico ../runner/torrentfile.ico - cp -rvf ./torrentfileQt ../runner/torrentfileQt - @echo "import torrentfileQt" >> ../runner/exe - @echo "torrentfileQt.start()" >> ../runner/exe - pyinstaller --distpath ../runner/dist --workpath ../runner/build \ - -F -n torrentfileQt -w -i ../runner/torrentfile.ico \ - --specpath ../runner/ ../runner/exe --log-level DEBUG --collect-data torrentfileQt - pyinstaller --distpath ../runner/dist --workpath ../runner/build \ - -D -n torrentfileQt -w -i ../runner/torrentfile.ico \ - --specpath ../runner/ ../runner/exe --log-level DEBUG --collect-data torrentfileQt - cp -rfv ../runner/dist/* ./dist/ - @python -c "$$FIXES" + rm -rfv .runner/dist + rm -rfv .runner/build + cd .runner && pyinstaller ./exec.spec + mkdir .runner/dist/torrentfileQt- full: clean test push release build diff --git a/README.md b/README.md index e4c4237..fb5ac36 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ TorrentFileQt is a GUI Frontend for [TorrentFile CLI](https://github.com/alexpde - Check progress or percentage complete for .torrent file - Edit torrent files. +## Requirements + +- Pyside6 +- torrentfile + ## ScreenShots ![createtorrent.png](./assets/screenshots/create-tab.png) @@ -37,3 +42,26 @@ TorrentFileQt is a GUI Frontend for [TorrentFile CLI](https://github.com/alexpde --------- ![torrentinfo.png](./assets/screenshots/info-tab.png) + +--------- + +## Install + +- From git: + +```bash +git clone https://github.com/alexpdev/torrentfileQt.git +cd torrentfileQt +pip install -r requirements.txt +pip install . +torrentfileQt +``` + +- From PyPi + +```bash +pip install torrentfileQt +torrentfileQt +``` + +> Alternatively you can download a precompiled binary from the release page. diff --git a/coverage.xml b/coverage.xml index f180339..7fbcc36 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,5 +1,5 @@ - + @@ -761,7 +761,7 @@ - + @@ -989,24 +989,24 @@ - - + + - - + + - - + + - - + + - - + + @@ -1028,20 +1028,20 @@ - + - + - - - + + + @@ -1049,18 +1049,18 @@ - - + + - + - - + + - + @@ -1070,17 +1070,18 @@ - + - - + + - + + - + @@ -1276,8 +1277,8 @@ - - + + @@ -1300,8 +1301,8 @@ - - + + @@ -1320,7 +1321,7 @@ - + @@ -1382,13 +1383,13 @@ - - - + + + - - + + @@ -1404,7 +1405,7 @@ - + @@ -1412,51 +1413,51 @@ - - - - + + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + + - @@ -1468,15 +1469,15 @@ + - + - - + @@ -1490,57 +1491,66 @@ + - + - - + + - - - + + + - + + - - - - - - - - - + + + + + + + + - - - - + + - + + + + + + + + + + + diff --git a/requirements.txt b/requirements.txt index cc6b56b..80c148f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,2 @@ -wheel -setuptools torrentfile==0.7.9 PySide6 -pyben diff --git a/tests/test_edittab.py b/tests/test_edittab.py index 2cb9bf1..374910b 100644 --- a/tests/test_edittab.py +++ b/tests/test_edittab.py @@ -123,7 +123,7 @@ def test_editor_table_fields(wind, ttorrent): for url in ['url8', 'url9']: wig.add_button.click() wig.line_edit.setText(url) - wig.combo.focusOutEvent(None) + wig.add_button.click() lst = [wig.combo.itemText(j) for j in range(wig.combo.count())] assert len([i for i in ['url8', 'url9'] if i in lst]) == 2 wig.remove_button.click() diff --git a/torrentfileQt/checkTab.py b/torrentfileQt/checkTab.py index 24050fe..41f9702 100644 --- a/torrentfileQt/checkTab.py +++ b/torrentfileQt/checkTab.py @@ -356,14 +356,15 @@ def __init__(self, parent=None): """Constructor for Tree Widget.""" super().__init__(parent=parent) self.window = parent.window - self.setColumnCount(3) - self.setIndentation(15) + self.setColumnCount(2) + self.setIndentation(12) self.item = self.invisibleRootItem() self.item.setExpanded(True) header = self.header() header.setSectionResizeMode(0, header.ResizeMode.ResizeToContents) header.setSectionResizeMode(1, header.ResizeMode.ResizeToContents) - self.setHeaderHidden(True) + header.setStretchLastSection(True) + self.setHeaderHidden(False) self.itemWidgets = {} self.paths = [] self.total = 0 diff --git a/torrentfileQt/createTab.py b/torrentfileQt/createTab.py index ff6aca3..bafd5d2 100644 --- a/torrentfileQt/createTab.py +++ b/torrentfileQt/createTab.py @@ -76,7 +76,7 @@ def __init__(self, parent=None): self.path_label = QLabel("Path: ", parent=self) self.output_label = QLabel("Save To: ", parent=self) - self.version_label = QLabel("Meta Version: ", parent=self) + self.version_label = QLabel("Version: ", parent=self) self.comment_label = QLabel("Comment: ", parent=self) self.announce_label = QLabel("Trackers: ", parent=self) self.web_seed_label = QLabel("Web-Seeds: ") @@ -406,10 +406,10 @@ def __init__(self, parent=None): def piece_length(cls, parent=None): """Create a piece_length combobox.""" box = cls(parent=parent) - for exp in range(14, 26): + for exp in range(14, 28): if exp < 20: - item = str((2**exp) // (2**10)) + "KB" + item = str((2**exp) // (2**10)) + " KiB" else: - item = str((2**exp) // (2**20)) + "MB" + item = str((2**exp) // (2**20)) + " MiB" box.addItem(item, 2**exp) return box diff --git a/torrentfileQt/editorTab.py b/torrentfileQt/editorTab.py index ddb2534..97d7a08 100644 --- a/torrentfileQt/editorTab.py +++ b/torrentfileQt/editorTab.py @@ -101,6 +101,7 @@ def dropEvent(self, event) -> bool: urls = event.mimeData().urls() path = urls[0].toLocalFile() if os.path.exists(path): + self.table.clear() self.line.setText(path) self.table.handleTorrent.emit(path) return True @@ -192,7 +193,7 @@ def add_item(self): self.box.insertItem(0, current, 2) self.box.insertItem(0, "", 2) self.box.setCurrentIndex(0) - self.box.lineEdit().setFocus() + self.parent.line_edit.setReadOnly(True) class RemoveItemButton(QToolButton): @@ -211,6 +212,7 @@ def remove_item(self): """Take action when button is pressed.""" index = self.box.currentIndex() self.box.removeItem(index) + self.parent.line_edit.setReadOnly(True) class Table(QTableWidget): @@ -300,18 +302,27 @@ class Combo(QComboBox): def __init__(self, parent=None): """Construct a combobox for table widget cell.""" super().__init__(parent=parent) + self.widget = parent self.setStyleSheet(table_styles["ComboBox"]) self.setInsertPolicy(self.InsertPolicy.InsertAtBottom) self.setDuplicatesEnabled(False) + self.widget.line_edit.setReadOnly(True) def focusOutEvent(self, _): """Add item when focus changes.""" + super().focusOutEvent(_) current = self.currentText().strip() items = [self.itemText(i) for i in range(self.count())] blanks = [i for i in range(len(items)) if not items[i].strip()] list(map(self.removeItem, blanks[::-1])) if current and current not in items: self.insertItem(0, current, 2) + self.widget.line_edit.setReadOnly(True) + + def focusInEvent(self, _): + """Make line edit widget active when clicking in to box.""" + super().focusInEvent(_) + self.widget.line_edit.setReadOnly(False) def __init__(self, parent=None): """Construct the widget and it's sub widgets.""" @@ -320,9 +331,9 @@ def __init__(self, parent=None): self.setLayout(self.layout) self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) - self.combo = self.Combo() self.line_edit = QLineEdit(parent=self) self.line_edit.setStyleSheet(table_styles["LineEdit"]) + self.combo = self.Combo(parent=self) self.layout.addWidget(self.combo) self.combo.setLineEdit(self.line_edit) self.add_button = AddItemButton(self) From 30e1cfdc9f55e17e97e9cec630ca562c3df19839 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 May 2022 20:16:30 -0700 Subject: [PATCH 3/4] updates to file paths and icons --- .gitignore | 1 - MANIFEST.in | 5 +++- Makefile | 9 ++++--- bin/exec | 3 +++ bin/exec.spec | 48 ++++++++++++++++++++++++++++++++++++++ coverage.xml | 28 +++++++--------------- torrentfileQt/checkTab.py | 2 +- torrentfileQt/createTab.py | 8 +++---- torrentfileQt/editorTab.py | 4 ++-- 9 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 bin/exec create mode 100644 bin/exec.spec diff --git a/.gitignore b/.gitignore index 6e01e5a..6b3388f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,6 @@ MANIFEST # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec # Installer logs pip-log.txt diff --git a/MANIFEST.in b/MANIFEST.in index 3f238d6..60b0360 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,8 +4,11 @@ include Makefile include package.json include requirements.txt -graft torrentfileQt/assets + graft torrentfileQt + +recursive-include torrentfileQt * +recursive-include torrentfileQt/assets * global-exclude *.py[cod] prune **/__pycache__ diff --git a/Makefile b/Makefile index 91172da..b41f327 100644 --- a/Makefile +++ b/Makefile @@ -67,12 +67,11 @@ release: clean test lint ## release to pypi python setup.py sdist bdist_wheel bdist_egg twine upload dist/* -build: clean +build: clean test lint python -m pip install --upgrade --no-cache --force-reinstall torrentfile pyben pip wheel setuptools python setup.py sdist bdist_wheel bdist_egg - rm -rfv .runner/dist - rm -rfv .runner/build - cd .runner && pyinstaller ./exec.spec - mkdir .runner/dist/torrentfileQt- + rm -rfv bin/dist + rm -rfv bin/build + cd bin && pyinstaller ./exec.spec full: clean test push release build diff --git a/bin/exec b/bin/exec new file mode 100644 index 0000000..0225060 --- /dev/null +++ b/bin/exec @@ -0,0 +1,3 @@ +import torrentfileQt + +torrentfileQt.start() diff --git a/bin/exec.spec b/bin/exec.spec new file mode 100644 index 0000000..9dd5b3e --- /dev/null +++ b/bin/exec.spec @@ -0,0 +1,48 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None +import os +from os.path import join, dirname, relpath + +assets = join(dirname(os.getcwd()), 'torrentfileQt', 'assets') +assets = os.path.relpath(assets,'.') +lst = [] +for i in [os.path.join(assets,i) for i in os.listdir(assets)]: + lst.append((i, 'torrentfileQt/assets')) + +a = Analysis(['exec'], + pathex=['../torrentfileQt'], + binaries=None, + datas=lst, + hiddenimports=None, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) + +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='torrentfileQt', + icon='../assets/torrentfile.ico', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None ) diff --git a/coverage.xml b/coverage.xml index 7fbcc36..f1e6c0b 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,5 +1,5 @@ - + @@ -761,7 +761,7 @@ - + @@ -1081,7 +1081,7 @@ - + @@ -1277,8 +1277,8 @@ - - + + @@ -1301,8 +1301,8 @@ - - + + @@ -1321,7 +1321,7 @@ - + @@ -1515,18 +1515,6 @@ - - - - - - - - - - - - diff --git a/torrentfileQt/checkTab.py b/torrentfileQt/checkTab.py index 41f9702..dc288dd 100644 --- a/torrentfileQt/checkTab.py +++ b/torrentfileQt/checkTab.py @@ -364,7 +364,7 @@ def __init__(self, parent=None): header.setSectionResizeMode(0, header.ResizeMode.ResizeToContents) header.setSectionResizeMode(1, header.ResizeMode.ResizeToContents) header.setStretchLastSection(True) - self.setHeaderHidden(False) + self.setHeaderHidden(True) self.itemWidgets = {} self.paths = [] self.total = 0 diff --git a/torrentfileQt/createTab.py b/torrentfileQt/createTab.py index bafd5d2..3be242c 100644 --- a/torrentfileQt/createTab.py +++ b/torrentfileQt/createTab.py @@ -337,9 +337,9 @@ def browse(self, path=None): self.window.output_input.setText(path + ".torrent") piece_length = path_piece_length(path) if piece_length < (2**20): - val = f"{piece_length//(2**10)}KB" + val = f"{piece_length//(2**10)} KiB" else: - val = f"{piece_length//(2**20)}MB" + val = f"{piece_length//(2**20)} MiB" for i in range(self.window.piece_length.count()): if self.window.piece_length.itemText(i) == val: self.window.piece_length.setCurrentIndex(i) @@ -382,9 +382,9 @@ def browse(self, path=None): except PermissionError: # pragma: no cover return if piece_length < (2**20): - val = f"{piece_length//(2**10)}KB" + val = f"{piece_length//(2**10)} KiB" else: # pragma: no cover - val = f"{piece_length//(2**20)}MB" + val = f"{piece_length//(2**20)} MiB" for i in range(self.window.piece_length.count()): if self.window.piece_length.itemText(i) == val: self.window.piece_length.setCurrentIndex(i) diff --git a/torrentfileQt/editorTab.py b/torrentfileQt/editorTab.py index 97d7a08..783070f 100644 --- a/torrentfileQt/editorTab.py +++ b/torrentfileQt/editorTab.py @@ -308,7 +308,7 @@ def __init__(self, parent=None): self.setDuplicatesEnabled(False) self.widget.line_edit.setReadOnly(True) - def focusOutEvent(self, _): + def focusOutEvent(self, _): # pragma: nocover """Add item when focus changes.""" super().focusOutEvent(_) current = self.currentText().strip() @@ -319,7 +319,7 @@ def focusOutEvent(self, _): self.insertItem(0, current, 2) self.widget.line_edit.setReadOnly(True) - def focusInEvent(self, _): + def focusInEvent(self, _): # pragma: nocover """Make line edit widget active when clicking in to box.""" super().focusInEvent(_) self.widget.line_edit.setReadOnly(False) From a5b91a16d4aa133cebdf1dde3ac7ae113e59eeb8 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 May 2022 20:23:02 -0700 Subject: [PATCH 4/4] bump version 3.5 --- coverage.xml | 2 +- package.json | 2 +- torrentfileQt/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coverage.xml b/coverage.xml index f1e6c0b..f9492b4 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,5 +1,5 @@ - + diff --git a/package.json b/package.json index 042c881..1d3b01a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "torrentfileqt", "displayName": "TorrentfileQt", - "version": "0.3.4", + "version": "0.3.5", "description": "GUI torrentfile creator.", "repository": { "type": "git", diff --git a/torrentfileQt/version.py b/torrentfileQt/version.py index bb29321..63bc40b 100644 --- a/torrentfileQt/version.py +++ b/torrentfileQt/version.py @@ -18,7 +18,7 @@ import os -__version__ = "0.3.4" +__version__ = "0.3.5" def _conf():