/
persepolis.py
431 lines (322 loc) · 16.4 KB
/
persepolis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# -*- coding: utf-8 -*-
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import platform
from copy import deepcopy
# finding os platform
os_type = platform.system()
# Don't run persepolis as root!
if os_type == 'Linux' or os_type == 'FreeBSD' or os_type == 'OpenBSD' or os_type == 'Darwin':
uid = os.getuid()
if uid == 0:
print('Do not run persepolis as root.')
sys.exit(1)
from persepolis.scripts import osCommands
import argparse
import struct
import json
# initialization
# find home address
home_address = os.path.expanduser("~")
# persepolis config_folder
if os_type == 'Linux' or os_type == 'FreeBSD' or os_type == 'OpenBSD':
config_folder = os.path.join(str(home_address), ".config/persepolis_download_manager")
elif os_type == 'Darwin':
config_folder = os.path.join(str(home_address), "Library/Application Support/persepolis_download_manager")
elif os_type == 'Windows' :
config_folder = os.path.join(str(home_address), 'AppData\Local\persepolis_download_manager')
# persepolis tmp folder path
persepolis_tmp = os.path.join(config_folder, 'persepolis_tmp')
# if lock_file_validation == True >> not another instanse running,
# else >> another instanse of persepolis is running now.
global lock_file_validation
if os_type != 'Windows':
import fcntl
user_name_split = home_address.split('/')
user_name = user_name_split[2]
# persepolis lock file
lock_file = '/tmp/persepolis_exec_' + user_name + '.lock'
# create lock file
fp = open(lock_file, 'w')
try:
fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
lock_file_validation = True # Lock file created successfully!
except IOError:
lock_file_validation = False # creating lock_file was unsuccessful! So persepolis is still running
else: # for windows
# pypiwin32 must be installed by pip
from win32event import CreateMutex
from win32api import GetLastError
from winerror import ERROR_ALREADY_EXISTS
from sys import exit
handle = CreateMutex(None, 1, 'persepolis_download_manager')
if GetLastError() == ERROR_ALREADY_EXISTS:
lock_file_validation = False
else:
lock_file_validation = True
# run persepolis mainwindow
if lock_file_validation:
from persepolis.scripts import initialization
from persepolis.scripts.mainwindow import MainWindow
# set "persepolis" name for this process in linux and bsd
if os_type == 'Linux' or os_type == 'FreeBSD' or os_type == 'OpenBSD':
try:
from setproctitle import setproctitle
setproctitle("persepolis")
except:
from persepolis.scripts import logger
logger.sendToLog('setproctitle is not installed!', "ERROR")
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QFont
from PyQt5.QtCore import QCoreApplication, QSettings
from persepolis.gui.palettes import DarkRedPallete, DarkBluePallete, ArcDarkRedPallete, ArcDarkBluePallete, LightRedPallete, LightBluePallete
from persepolis.scripts.bubble import notifySend
from persepolis.scripts.error_window import ErrorWindow
import traceback
# load persepolis_settings
persepolis_setting = QSettings('persepolis_download_manager', 'persepolis')
class PersepolisApplication(QApplication):
def __init__(self, argv):
super().__init__(argv)
def setPersepolisStyle(self, style):
# set style
self.persepolis_style = style
self.setStyle(style)
def setPersepolisFont(self, font, font_size, custom_font):
# font and font_size
self.persepolis_font = font
self.persepolis_font_size = font_size
if custom_font == 'yes':
self.setFont(QFont(font , font_size ))
# color_scheme
def setPersepolisColorScheme(self, color_scheme):
self.persepolis_color_scheme = color_scheme
if color_scheme == 'Persepolis Dark Red':
persepolis_dark_red = DarkRedPallete()
self.setPalette(persepolis_dark_red)
self.setStyleSheet("QMenu::item:selected {background-color : #d64937 ;color : white} QToolTip { color: #ffffff; background-color: #353535; border: 1px solid white; }")
elif color_scheme == 'Persepolis Dark Blue':
persepolis_dark_blue = DarkBluePallete()
self.setPalette(persepolis_dark_blue)
self.setStyleSheet("QMenu::item:selected { background-color : #2a82da ;color : white } QToolTip { color: #ffffff; background-color: #353535; border: 1px solid white; }")
elif color_scheme == 'Persepolis ArcDark Red':
persepolis_arcdark_red = ArcDarkRedPallete()
self.setPalette(persepolis_arcdark_red)
self.setStyleSheet("QMenu::item:selected {background-color : #bf474d ; color : white} QToolTip { color: #ffffff; background-color: #353945; border: 1px solid white; } QPushButton {background-color: #353945 } QTabWidget {background-color : #353945;} QMenu {background-color: #353945 }")
elif color_scheme == 'Persepolis ArcDark Blue':
persepolis_arcdark_blue = ArcDarkBluePallete()
self.setPalette(persepolis_arcdark_blue)
self.setStyleSheet("QMenu::item:selected {background-color : #5294e2 ; color : white } QToolTip { color: #ffffff; background-color: #353945; border: 1px solid white; } QPushButton {background-color: #353945 } QTabWidget {background-color : #353945;} QMenu {background-color: #353945 }")
elif color_scheme == 'Persepolis Light Red':
persepolis_light_red = LightRedPallete()
self.setPalette(persepolis_light_red)
self.setStyleSheet("QMenu::item:selected {background-color : #d64937 ;color : white} QToolTip { color: #ffffff; background-color: #353535; border: 1px solid white; }")
elif color_scheme == 'Persepolis Light Blue':
persepolis_light_blue = LightBluePallete()
self.setPalette(persepolis_light_blue)
self.setStyleSheet("QMenu::item:selected { background-color : #2a82da ;color : white } QToolTip { color: #ffffff; background-color: #353535; border: 1px solid white; }")
# create terminal arguments
parser = argparse.ArgumentParser(description='Persepolis Download Manager')
#parser.add_argument('chromium', nargs = '?', default = 'no', help='this switch is used for chrome native messaging in Linux and Mac')
parser.add_argument('--link', action='store', nargs = 1, help='Download link.(Use "" for links)')
parser.add_argument('--referer', action='store', nargs = 1, help='Set an http referrer (Referer). This affects all http/https downloads. If * is given, the download URI is also used as the referrer.')
parser.add_argument('--cookie', action='store', nargs = 1, help='Cookie')
parser.add_argument('--agent', action='store', nargs = 1, help='Set user agent for HTTP(S) downloads. Default: aria2/$VERSION, $VERSION is replaced by package version.')
parser.add_argument('--headers',action='store', nargs = 1, help='Append HEADER to HTTP request header. ')
parser.add_argument('--name', action='store', nargs = 1, help='The file name of the downloaded file. ')
parser.add_argument('--default', action='store_true', help='restore default setting')
parser.add_argument('--clear', action='store_true', help='Clear download list and user setting!')
parser.add_argument('--tray', action='store_true', help="Persepolis is starting in tray icon. It's useful when you want to put persepolis in system's startup.")
parser.add_argument('--parent-window', action='store', nargs = 1, help='this switch is used for chrome native messaging in Windows')
parser.add_argument('--version', action='version', version='Persepolis Download Manager 3.0.1')
parser.add_argument('args', nargs=argparse.REMAINDER)
#args, unknown = parser.parse_known_args(['chromium','--link','--referer','--cookie','--agent','--headers','--name','--default','--clear','--tray','--parent-window','--version'])
args = parser.parse_args()
# terminal arguments are send download information with terminal arguments(link , referer , cookie , agent , headers , name )
# persepolis plugins (for chromium and chrome and opera and vivaldi and firefox) are use native message host system for
# sending download information to persepolis.
# see this repo for more information:
# https://github.com/persepolisdm/Persepolis-WebExtension
# if --execute >> yes >>> persepolis main window will start.
# if --execute >> no >>> persepolis started before!
add_link_dictionary = {}
plugin_list = []
browser_plugin_dict ={'link': None,
'referer': None,
'load_cookies':None,
'user_agent': None,
'header': None,
'out': None
}
#if args.chromium != 'no' or args.parent_window:
if args.parent_window or args.args:
# Platform specific configuration
if os_type == "Windows":
# Set the default I/O mode to O_BINARY in windows
import msvcrt
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
# Send message to browsers plugin
message = '{"enable": true, "version": "1.85"}'.encode('utf-8')
sys.stdout.buffer.write((struct.pack('i', len(message))))
sys.stdout.buffer.write(message)
sys.stdout.flush()
text_length_bytes = sys.stdin.buffer.read(4)
# Unpack message length as 4 byte integer.
text_length = struct.unpack('@I', text_length_bytes)[0]
# Read the text (JSON object) of the message.
text = sys.stdin.buffer.read(text_length).decode("utf-8")
if text:
new_list = json.loads(text)
for item in new_list['url_links']:
copy_dict = deepcopy(browser_plugin_dict)
if 'url' in item.keys():
copy_dict['link'] = str(item['url'])
if 'referrer' in item.keys() and item['referrer'] != '':
copy_dict['referer'] = item['referrer']
if 'filename' in item.keys() and item['filename'] != '':
copy_dict['out'] = os.path.basename(str(item['filename']))
if 'useragent' in item.keys() and item['useragent'] != '':
copy_dict['user_agent'] = item['useragent']
if 'cookies' in item.keys() and item['cookies'] != '':
copy_dict['load_cookies'] = item['cookies']
plugin_list.append(copy_dict)
# persepolis --clear >> remove config_folder
if args.clear:
from persepolis.scripts.data_base import PersepolisDB
# create an object for PersepolisDB
persepolis_db = PersepolisDB()
# Reset data base
persepolis_db.resetDataBase()
# close connections
persepolis_db.closeConnections()
# Reset persepolis_setting
persepolis_setting = QSettings('persepolis_download_manager', 'persepolis')
persepolis_setting.clear()
persepolis_setting.sync()
sys.exit(0)
# persepolis --default >> remove persepolis setting.
if args.default:
persepolis_setting = QSettings('persepolis_download_manager', 'persepolis')
persepolis_setting.clear()
persepolis_setting.sync()
print ('Persepolis restored default')
sys.exit(0)
if args.link :
add_link_dictionary ['link'] = "".join(args.link)
# if plugins call persepolis, then just start persepolis in system tray
args.tray = True
if args.referer :
add_link_dictionary['referer'] = "".join(args.referer)
else:
add_link_dictionary['referer'] = None
if args.cookie :
add_link_dictionary['load_cookies'] = "".join(args.cookie)
else:
add_link_dictionary['load_cookies'] = None
if args.agent :
add_link_dictionary['user_agent'] = "".join(args.agent)
else:
add_link_dictionary['user_agent'] = None
if args.headers :
add_link_dictionary['header'] = "".join(args.headers)
else:
add_link_dictionary['header'] = None
if args.name :
add_link_dictionary ['out'] = "".join(args.name)
else:
add_link_dictionary['out'] = None
if args.tray:
start_in_tray = True
else:
start_in_tray = False
# when browsers plugin calls persepolis or user runs persepolis by terminal arguments,
# then persepolis creats a request file in persepolis_tmp folder and link information added to
# plugins_db.db file(see data_base.py for more information).
# persepolis mainwindow checks persepolis_tmp for plugins request file every 2 seconds (see CheckingThread class in mainwindow.py)
# when requset received in CheckingThread, a popup window (AddLinkWindow) comes up and window gets additional download information
# from user (port , proxy , ...) and download starts and request file deleted
if ('link' in add_link_dictionary.keys()):
plugin_dict ={'link': add_link_dictionary['link'],
'referer': add_link_dictionary['referer'],
'load_cookies': add_link_dictionary['load_cookies'],
'user_agent': add_link_dictionary['user_agent'],
'header': add_link_dictionary['header'],
'out': add_link_dictionary['out']
}
plugin_list.append(plugin_dict)
if len(plugin_list) != 0:
# import PluginsDB
from persepolis.scripts.data_base import PluginsDB
# create an object for PluginsDB
plugins_db = PluginsDB()
# add plugin_list to plugins_table in plugins.db file.
plugins_db.insertInPluginsTable(plugin_list)
# Job is done! close connections.
plugins_db.closeConnections()
# notify that a link is added!
plugin_ready = os.path.join(persepolis_tmp, 'persepolis-plugin-ready')
osCommands.touch(plugin_ready)
# start persepolis in system tray
start_in_tray = True
def main():
# if lock_file is existed , it means persepolis is still running!
if lock_file_validation:
# run mainwindow
# set color_scheme and style
# see palettes.py and setting.py
persepolis_download_manager = PersepolisApplication(sys.argv)
# set organization name and domain and apllication name
QCoreApplication.setOrganizationName('persepolis_download_manager')
QCoreApplication.setApplicationName('persepolis')
# Persepolis setting
persepolis_download_manager.setting = QSettings()
# get user's desired font and style , ... from setting
custom_font = persepolis_download_manager.setting.value('settings/custom-font')
font = persepolis_download_manager.setting.value('settings/font')
font_size = int(persepolis_download_manager.setting.value('settings/font-size'))
style = persepolis_download_manager.setting.value('settings/style')
color_scheme = persepolis_download_manager.setting.value('settings/color-scheme')
# set style
persepolis_download_manager.setPersepolisStyle(style)
# set font
persepolis_download_manager.setPersepolisFont(font, font_size, custom_font)
# set color_scheme
persepolis_download_manager.setPersepolisColorScheme(color_scheme)
# run mainwindow
try:
mainwindow = MainWindow(start_in_tray, persepolis_download_manager, persepolis_download_manager.setting)
if start_in_tray:
mainwindow.hide()
else:
mainwindow.show()
except Exception:
from persepolis.scripts import logger
error_message = str(traceback.format_exc())
# write error_message in log file.
logger.sendToLog(error_message, "ERROR")
# Reset persepolis
error_window = ErrorWindow(error_message)
error_window.show()
sys.exit(persepolis_download_manager.exec_())
else:
# this section warns user that program is still running and no need to run it again
# and creating a file to notify mainwindow for showing itself!
# (see CheckingThread in mainwindow.py for more information)
if len(plugin_list) == 0:
show_window_file = os.path.join(persepolis_tmp, 'show-window')
f = open(show_window_file, 'w')
f.close()
sys.exit(0)