mirror of
https://github.com/rembo10/headphones.git
synced 2026-07-21 08:24:00 +01:00
Converting crlf to just lf
Making end-of-lines consistent throughout the codebase.
This commit is contained in:
@@ -1,63 +1,63 @@
|
|||||||
#Headphones
|
#Headphones
|
||||||
|
|
||||||
###Support & Discuss
|
###Support & Discuss
|
||||||
|
|
||||||
You are free to join the HP support community on IRC where you can ask questions, hang around and discuss anything related to HP.
|
You are free to join the HP support community on IRC where you can ask questions, hang around and discuss anything related to HP.
|
||||||
|
|
||||||
1. Use any IRC client and connect to the Freenode server.
|
1. Use any IRC client and connect to the Freenode server.
|
||||||
2. Join #headphones
|
2. Join #headphones
|
||||||
|
|
||||||
###Installation and Notes
|
###Installation and Notes
|
||||||
|
|
||||||
[Read our Wiki](../../wiki) on how to install and use HeadPhones properly.
|
[Read our Wiki](../../wiki) on how to install and use HeadPhones properly.
|
||||||
|
|
||||||
**Issues** can be reported on the GitHub issue tracker considering these rules:
|
**Issues** can be reported on the GitHub issue tracker considering these rules:
|
||||||
|
|
||||||
1. Analyze your log, you just might find the solution yourself!
|
1. Analyze your log, you just might find the solution yourself!
|
||||||
2. You read the wiki and searched existing issues, but this is not solving your problem.
|
2. You read the wiki and searched existing issues, but this is not solving your problem.
|
||||||
3. Post the issue with a clear title, description and the HP log and use [proper markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your text (code/log in code blocks).
|
3. Post the issue with a clear title, description and the HP log and use [proper markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your text (code/log in code blocks).
|
||||||
4. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
4. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
||||||
|
|
||||||
**Feature requests** can be reported on the GitHub issue tracker too:
|
**Feature requests** can be reported on the GitHub issue tracker too:
|
||||||
|
|
||||||
1. Search for similar existing 'issues', feature requests can be recognized by the label 'Request'.
|
1. Search for similar existing 'issues', feature requests can be recognized by the label 'Request'.
|
||||||
2. If a similar Request exists, post a comment (+1, or add a new idea to the existing request), otherwise you can create a new one.
|
2. If a similar Request exists, post a comment (+1, or add a new idea to the existing request), otherwise you can create a new one.
|
||||||
|
|
||||||
If you **comply with these rules** you can [post your request/issue](http://github.com/rembo10/headphones/issues).
|
If you **comply with these rules** you can [post your request/issue](http://github.com/rembo10/headphones/issues).
|
||||||
|
|
||||||
**Support** the project by implementing new features, solving support tickets and provide bug fixes.
|
**Support** the project by implementing new features, solving support tickets and provide bug fixes.
|
||||||
If you change something in the code always make a PR to the developer branch instead of the master branch.
|
If you change something in the code always make a PR to the developer branch instead of the master branch.
|
||||||
|
|
||||||
|
|
||||||
###Screenshots
|
###Screenshots
|
||||||
|
|
||||||
Homepage (Artist Overview)
|
Homepage (Artist Overview)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
One of the many settings pages....
|
One of the many settings pages....
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
It might even know you better than you know yourself:
|
It might even know you better than you know yourself:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Import Your Favorite Artists:
|
Import Your Favorite Artists:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Artist Search Results (also search by album!):
|
Artist Search Results (also search by album!):
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Artist Page with Bio & Album Overview:
|
Artist Page with Bio & Album Overview:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Album Page with track overview:
|
Album Page with track overview:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
This is free software under the GPL v3 open source license - so feel free to do with it what you wish.
|
This is free software under the GPL v3 open source license - so feel free to do with it what you wish.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
@base-font-face: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
|
@base-font-face: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
|
||||||
@alt-font-face: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
@alt-font-face: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
||||||
@base-font-size: 12px;
|
@base-font-size: 12px;
|
||||||
@@ -10,20 +10,20 @@
|
|||||||
@msg-bg: #FFF6A9;
|
@msg-bg: #FFF6A9;
|
||||||
@msg-bg-success: #D3FFD7;
|
@msg-bg-success: #D3FFD7;
|
||||||
@msg-bg-error: #FFD3D3;
|
@msg-bg-error: #FFD3D3;
|
||||||
|
|
||||||
/* Mixins */
|
/* Mixins */
|
||||||
.rounded(@radius: 5px) {
|
.rounded(@radius: 5px) {
|
||||||
-moz-border-radius: @radius;
|
-moz-border-radius: @radius;
|
||||||
-webkit-border-radius: @radius;
|
-webkit-border-radius: @radius;
|
||||||
border-radius: @radius;
|
border-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedTop(@radius: 5px) {
|
.roundedTop(@radius: 5px) {
|
||||||
-moz-border-radius-topleft: @radius;
|
-moz-border-radius-topleft: @radius;
|
||||||
-moz-border-radius-topright: @radius;
|
-moz-border-radius-topright: @radius;
|
||||||
-webkit-border-top-right-radius: @radius;
|
-webkit-border-top-right-radius: @radius;
|
||||||
-webkit-border-top-left-radius: @radius;
|
-webkit-border-top-left-radius: @radius;
|
||||||
border-top-left-radius: @radius;
|
border-top-left-radius: @radius;
|
||||||
border-top-right-radius: @radius;
|
border-top-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedLeftTop(@radius: 5px) {
|
.roundedLeftTop(@radius: 5px) {
|
||||||
-moz-border-radius-topleft: @radius;
|
-moz-border-radius-topleft: @radius;
|
||||||
@@ -34,14 +34,14 @@
|
|||||||
-moz-border-radius-topright: @radius;
|
-moz-border-radius-topright: @radius;
|
||||||
-webkit-border-top-right-radius: @radius;
|
-webkit-border-top-right-radius: @radius;
|
||||||
border-top-right-radius: @radius;
|
border-top-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedBottom(@radius: 5px) {
|
.roundedBottom(@radius: 5px) {
|
||||||
-moz-border-radius-bottomleft: @radius;
|
-moz-border-radius-bottomleft: @radius;
|
||||||
-moz-border-radius-bottomright: @radius;
|
-moz-border-radius-bottomright: @radius;
|
||||||
-webkit-border-bottom-right-radius: @radius;
|
-webkit-border-bottom-right-radius: @radius;
|
||||||
-webkit-border-bottom-left-radius: @radius;
|
-webkit-border-bottom-left-radius: @radius;
|
||||||
border-bottom-left-radius: @radius;
|
border-bottom-left-radius: @radius;
|
||||||
border-bottom-right-radius: @radius;
|
border-bottom-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedLeftBottom(@radius: 5px) {
|
.roundedLeftBottom(@radius: 5px) {
|
||||||
-moz-border-radius-bottomleft: @radius;
|
-moz-border-radius-bottomleft: @radius;
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
-moz-border-radius-bottomright: @radius;
|
-moz-border-radius-bottomright: @radius;
|
||||||
-webkit-border-bottom-right-radius: @radius;
|
-webkit-border-bottom-right-radius: @radius;
|
||||||
border-bottom-right-radius: @radius;
|
border-bottom-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.shadow(@shadow: 0 17px 11px -1px #ced8d9) {
|
.shadow(@shadow: 0 17px 11px -1px #ced8d9) {
|
||||||
-moz-box-shadow: @shadow;
|
-moz-box-shadow: @shadow;
|
||||||
-webkit-box-shadow: @shadow;
|
-webkit-box-shadow: @shadow;
|
||||||
@@ -74,4 +74,4 @@
|
|||||||
-o-opacity:@opacity_percent / 100 !important;
|
-o-opacity:@opacity_percent / 100 !important;
|
||||||
opacity:@opacity_percent / 100 !important;
|
opacity:@opacity_percent / 100 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+130
-130
@@ -1,130 +1,130 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#########################################
|
#########################################
|
||||||
## Stolen from Sick-Beard's classes.py ##
|
## Stolen from Sick-Beard's classes.py ##
|
||||||
#########################################
|
#########################################
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from common import USER_AGENT
|
from common import USER_AGENT
|
||||||
|
|
||||||
class HeadphonesURLopener(urllib.FancyURLopener):
|
class HeadphonesURLopener(urllib.FancyURLopener):
|
||||||
version = USER_AGENT
|
version = USER_AGENT
|
||||||
|
|
||||||
class AuthURLOpener(HeadphonesURLopener):
|
class AuthURLOpener(HeadphonesURLopener):
|
||||||
"""
|
"""
|
||||||
URLOpener class that supports http auth without needing interactive password entry.
|
URLOpener class that supports http auth without needing interactive password entry.
|
||||||
If the provided username/password don't work it simply fails.
|
If the provided username/password don't work it simply fails.
|
||||||
|
|
||||||
user: username to use for HTTP auth
|
user: username to use for HTTP auth
|
||||||
pw: password to use for HTTP auth
|
pw: password to use for HTTP auth
|
||||||
"""
|
"""
|
||||||
def __init__(self, user, pw):
|
def __init__(self, user, pw):
|
||||||
self.username = user
|
self.username = user
|
||||||
self.password = pw
|
self.password = pw
|
||||||
|
|
||||||
# remember if we've tried the username/password before
|
# remember if we've tried the username/password before
|
||||||
self.numTries = 0
|
self.numTries = 0
|
||||||
|
|
||||||
# call the base class
|
# call the base class
|
||||||
urllib.FancyURLopener.__init__(self)
|
urllib.FancyURLopener.__init__(self)
|
||||||
|
|
||||||
def prompt_user_passwd(self, host, realm):
|
def prompt_user_passwd(self, host, realm):
|
||||||
"""
|
"""
|
||||||
Override this function and instead of prompting just give the
|
Override this function and instead of prompting just give the
|
||||||
username/password that were provided when the class was instantiated.
|
username/password that were provided when the class was instantiated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# if this is the first try then provide a username/password
|
# if this is the first try then provide a username/password
|
||||||
if self.numTries == 0:
|
if self.numTries == 0:
|
||||||
self.numTries = 1
|
self.numTries = 1
|
||||||
return (self.username, self.password)
|
return (self.username, self.password)
|
||||||
|
|
||||||
# if we've tried before then return blank which cancels the request
|
# if we've tried before then return blank which cancels the request
|
||||||
else:
|
else:
|
||||||
return ('', '')
|
return ('', '')
|
||||||
|
|
||||||
# this is pretty much just a hack for convenience
|
# this is pretty much just a hack for convenience
|
||||||
def openit(self, url):
|
def openit(self, url):
|
||||||
self.numTries = 0
|
self.numTries = 0
|
||||||
return HeadphonesURLopener.open(self, url)
|
return HeadphonesURLopener.open(self, url)
|
||||||
|
|
||||||
class SearchResult:
|
class SearchResult:
|
||||||
"""
|
"""
|
||||||
Represents a search result from an indexer.
|
Represents a search result from an indexer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.provider = -1
|
self.provider = -1
|
||||||
|
|
||||||
# URL to the NZB/torrent file
|
# URL to the NZB/torrent file
|
||||||
self.url = ""
|
self.url = ""
|
||||||
|
|
||||||
# used by some providers to store extra info associated with the result
|
# used by some providers to store extra info associated with the result
|
||||||
self.extraInfo = []
|
self.extraInfo = []
|
||||||
|
|
||||||
# quality of the release
|
# quality of the release
|
||||||
self.quality = -1
|
self.quality = -1
|
||||||
|
|
||||||
# release name
|
# release name
|
||||||
self.name = ""
|
self.name = ""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
||||||
if self.provider == None:
|
if self.provider == None:
|
||||||
return "Invalid provider, unable to print self"
|
return "Invalid provider, unable to print self"
|
||||||
|
|
||||||
myString = self.provider.name + " @ " + self.url + "\n"
|
myString = self.provider.name + " @ " + self.url + "\n"
|
||||||
myString += "Extra Info:\n"
|
myString += "Extra Info:\n"
|
||||||
for extra in self.extraInfo:
|
for extra in self.extraInfo:
|
||||||
myString += " " + extra + "\n"
|
myString += " " + extra + "\n"
|
||||||
return myString
|
return myString
|
||||||
|
|
||||||
class NZBSearchResult(SearchResult):
|
class NZBSearchResult(SearchResult):
|
||||||
"""
|
"""
|
||||||
Regular NZB result with an URL to the NZB
|
Regular NZB result with an URL to the NZB
|
||||||
"""
|
"""
|
||||||
resultType = "nzb"
|
resultType = "nzb"
|
||||||
|
|
||||||
class NZBDataSearchResult(SearchResult):
|
class NZBDataSearchResult(SearchResult):
|
||||||
"""
|
"""
|
||||||
NZB result where the actual NZB XML data is stored in the extraInfo
|
NZB result where the actual NZB XML data is stored in the extraInfo
|
||||||
"""
|
"""
|
||||||
resultType = "nzbdata"
|
resultType = "nzbdata"
|
||||||
|
|
||||||
class TorrentSearchResult(SearchResult):
|
class TorrentSearchResult(SearchResult):
|
||||||
"""
|
"""
|
||||||
Torrent result with an URL to the torrent
|
Torrent result with an URL to the torrent
|
||||||
"""
|
"""
|
||||||
resultType = "torrent"
|
resultType = "torrent"
|
||||||
|
|
||||||
class Proper:
|
class Proper:
|
||||||
def __init__(self, name, url, date):
|
def __init__(self, name, url, date):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.url = url
|
self.url = url
|
||||||
self.date = date
|
self.date = date
|
||||||
self.provider = None
|
self.provider = None
|
||||||
self.quality = -1
|
self.quality = -1
|
||||||
|
|
||||||
self.tvdbid = -1
|
self.tvdbid = -1
|
||||||
self.season = -1
|
self.season = -1
|
||||||
self.episode = -1
|
self.episode = -1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.date)+" "+self.name+" "+str(self.season)+"x"+str(self.episode)+" of "+str(self.tvdbid)
|
return str(self.date)+" "+self.name+" "+str(self.season)+"x"+str(self.episode)+" of "+str(self.tvdbid)
|
||||||
|
|||||||
+177
-177
@@ -1,177 +1,177 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Created on Aug 1, 2011
|
Created on Aug 1, 2011
|
||||||
|
|
||||||
@author: Michael
|
@author: Michael
|
||||||
'''
|
'''
|
||||||
import platform, operator, os, re
|
import platform, operator, os, re
|
||||||
|
|
||||||
from headphones import version
|
from headphones import version
|
||||||
|
|
||||||
#Identify Our Application
|
#Identify Our Application
|
||||||
USER_AGENT = 'Headphones/-'+version.HEADPHONES_VERSION+' ('+platform.system()+' '+platform.release()+')'
|
USER_AGENT = 'Headphones/-'+version.HEADPHONES_VERSION+' ('+platform.system()+' '+platform.release()+')'
|
||||||
|
|
||||||
### Notification Types
|
### Notification Types
|
||||||
NOTIFY_SNATCH = 1
|
NOTIFY_SNATCH = 1
|
||||||
NOTIFY_DOWNLOAD = 2
|
NOTIFY_DOWNLOAD = 2
|
||||||
|
|
||||||
notifyStrings = {}
|
notifyStrings = {}
|
||||||
notifyStrings[NOTIFY_SNATCH] = "Started Download"
|
notifyStrings[NOTIFY_SNATCH] = "Started Download"
|
||||||
notifyStrings[NOTIFY_DOWNLOAD] = "Download Finished"
|
notifyStrings[NOTIFY_DOWNLOAD] = "Download Finished"
|
||||||
|
|
||||||
### Release statuses
|
### Release statuses
|
||||||
UNKNOWN = -1 # should never happen
|
UNKNOWN = -1 # should never happen
|
||||||
UNAIRED = 1 # releases that haven't dropped yet
|
UNAIRED = 1 # releases that haven't dropped yet
|
||||||
SNATCHED = 2 # qualified with quality
|
SNATCHED = 2 # qualified with quality
|
||||||
WANTED = 3 # releases we don't have but want to get
|
WANTED = 3 # releases we don't have but want to get
|
||||||
DOWNLOADED = 4 # qualified with quality
|
DOWNLOADED = 4 # qualified with quality
|
||||||
SKIPPED = 5 # releases we don't want
|
SKIPPED = 5 # releases we don't want
|
||||||
ARCHIVED = 6 # releases that you don't have locally (counts toward download completion stats)
|
ARCHIVED = 6 # releases that you don't have locally (counts toward download completion stats)
|
||||||
IGNORED = 7 # releases that you don't want included in your download stats
|
IGNORED = 7 # releases that you don't want included in your download stats
|
||||||
SNATCHED_PROPER = 9 # qualified with quality
|
SNATCHED_PROPER = 9 # qualified with quality
|
||||||
|
|
||||||
class Quality:
|
class Quality:
|
||||||
|
|
||||||
NONE = 0
|
NONE = 0
|
||||||
B192 = 1<<1 # 2
|
B192 = 1<<1 # 2
|
||||||
VBR = 1<<2 # 4
|
VBR = 1<<2 # 4
|
||||||
B256 = 1<<3 # 8
|
B256 = 1<<3 # 8
|
||||||
B320 = 1<<4 #16
|
B320 = 1<<4 #16
|
||||||
FLAC = 1<<5 #32
|
FLAC = 1<<5 #32
|
||||||
|
|
||||||
# put these bits at the other end of the spectrum, far enough out that they shouldn't interfere
|
# put these bits at the other end of the spectrum, far enough out that they shouldn't interfere
|
||||||
UNKNOWN = 1<<15
|
UNKNOWN = 1<<15
|
||||||
|
|
||||||
qualityStrings = {NONE: "N/A",
|
qualityStrings = {NONE: "N/A",
|
||||||
UNKNOWN: "Unknown",
|
UNKNOWN: "Unknown",
|
||||||
B192: "MP3 192",
|
B192: "MP3 192",
|
||||||
VBR: "MP3 VBR",
|
VBR: "MP3 VBR",
|
||||||
B256: "MP3 256",
|
B256: "MP3 256",
|
||||||
B320: "MP3 320",
|
B320: "MP3 320",
|
||||||
FLAC: "Flac"}
|
FLAC: "Flac"}
|
||||||
|
|
||||||
statusPrefixes = {DOWNLOADED: "Downloaded",
|
statusPrefixes = {DOWNLOADED: "Downloaded",
|
||||||
SNATCHED: "Snatched"}
|
SNATCHED: "Snatched"}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _getStatusStrings(status):
|
def _getStatusStrings(status):
|
||||||
toReturn = {}
|
toReturn = {}
|
||||||
for x in Quality.qualityStrings.keys():
|
for x in Quality.qualityStrings.keys():
|
||||||
toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")"
|
toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")"
|
||||||
return toReturn
|
return toReturn
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def combineQualities(anyQualities, bestQualities):
|
def combineQualities(anyQualities, bestQualities):
|
||||||
anyQuality = 0
|
anyQuality = 0
|
||||||
bestQuality = 0
|
bestQuality = 0
|
||||||
if anyQualities:
|
if anyQualities:
|
||||||
anyQuality = reduce(operator.or_, anyQualities)
|
anyQuality = reduce(operator.or_, anyQualities)
|
||||||
if bestQualities:
|
if bestQualities:
|
||||||
bestQuality = reduce(operator.or_, bestQualities)
|
bestQuality = reduce(operator.or_, bestQualities)
|
||||||
return anyQuality | (bestQuality<<16)
|
return anyQuality | (bestQuality<<16)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def splitQuality(quality):
|
def splitQuality(quality):
|
||||||
anyQualities = []
|
anyQualities = []
|
||||||
bestQualities = []
|
bestQualities = []
|
||||||
for curQual in Quality.qualityStrings.keys():
|
for curQual in Quality.qualityStrings.keys():
|
||||||
if curQual & quality:
|
if curQual & quality:
|
||||||
anyQualities.append(curQual)
|
anyQualities.append(curQual)
|
||||||
if curQual<<16 & quality:
|
if curQual<<16 & quality:
|
||||||
bestQualities.append(curQual)
|
bestQualities.append(curQual)
|
||||||
|
|
||||||
return (anyQualities, bestQualities)
|
return (anyQualities, bestQualities)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def nameQuality(name):
|
def nameQuality(name):
|
||||||
|
|
||||||
name = os.path.basename(name)
|
name = os.path.basename(name)
|
||||||
|
|
||||||
# if we have our exact text then assume we put it there
|
# if we have our exact text then assume we put it there
|
||||||
for x in Quality.qualityStrings:
|
for x in Quality.qualityStrings:
|
||||||
if x == Quality.UNKNOWN:
|
if x == Quality.UNKNOWN:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W'
|
regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W'
|
||||||
regex_match = re.search(regex, name, re.I)
|
regex_match = re.search(regex, name, re.I)
|
||||||
if regex_match:
|
if regex_match:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
checkName = lambda list, func: func([re.search(x, name, re.I) for x in list])
|
checkName = lambda list, func: func([re.search(x, name, re.I) for x in list])
|
||||||
|
|
||||||
#TODO: fix quality checking here
|
#TODO: fix quality checking here
|
||||||
if checkName(["mp3", "192"], any) and not checkName(["flac"], all):
|
if checkName(["mp3", "192"], any) and not checkName(["flac"], all):
|
||||||
return Quality.B192
|
return Quality.B192
|
||||||
elif checkName(["mp3", "256"], any) and not checkName(["flac"], all):
|
elif checkName(["mp3", "256"], any) and not checkName(["flac"], all):
|
||||||
return Quality.B256
|
return Quality.B256
|
||||||
elif checkName(["mp3", "vbr"], any) and not checkName(["flac"], all):
|
elif checkName(["mp3", "vbr"], any) and not checkName(["flac"], all):
|
||||||
return Quality.VBR
|
return Quality.VBR
|
||||||
elif checkName(["mp3", "320"], any) and not checkName(["flac"], all):
|
elif checkName(["mp3", "320"], any) and not checkName(["flac"], all):
|
||||||
return Quality.B320
|
return Quality.B320
|
||||||
else:
|
else:
|
||||||
return Quality.UNKNOWN
|
return Quality.UNKNOWN
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def assumeQuality(name):
|
def assumeQuality(name):
|
||||||
|
|
||||||
if name.lower().endswith(".mp3"):
|
if name.lower().endswith(".mp3"):
|
||||||
return Quality.MP3
|
return Quality.MP3
|
||||||
elif name.lower().endswith(".flac"):
|
elif name.lower().endswith(".flac"):
|
||||||
return Quality.LOSSLESS
|
return Quality.LOSSLESS
|
||||||
else:
|
else:
|
||||||
return Quality.UNKNOWN
|
return Quality.UNKNOWN
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compositeStatus(status, quality):
|
def compositeStatus(status, quality):
|
||||||
return status + 100 * quality
|
return status + 100 * quality
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def qualityDownloaded(status):
|
def qualityDownloaded(status):
|
||||||
return (status - DOWNLOADED) / 100
|
return (status - DOWNLOADED) / 100
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def splitCompositeStatus(status):
|
def splitCompositeStatus(status):
|
||||||
"""Returns a tuple containing (status, quality)"""
|
"""Returns a tuple containing (status, quality)"""
|
||||||
for x in sorted(Quality.qualityStrings.keys(), reverse=True):
|
for x in sorted(Quality.qualityStrings.keys(), reverse=True):
|
||||||
if status > x*100:
|
if status > x*100:
|
||||||
return (status-x*100, x)
|
return (status-x*100, x)
|
||||||
|
|
||||||
return (Quality.NONE, status)
|
return (Quality.NONE, status)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def statusFromName(name, assume=True):
|
def statusFromName(name, assume=True):
|
||||||
quality = Quality.nameQuality(name)
|
quality = Quality.nameQuality(name)
|
||||||
if assume and quality == Quality.UNKNOWN:
|
if assume and quality == Quality.UNKNOWN:
|
||||||
quality = Quality.assumeQuality(name)
|
quality = Quality.assumeQuality(name)
|
||||||
return Quality.compositeStatus(DOWNLOADED, quality)
|
return Quality.compositeStatus(DOWNLOADED, quality)
|
||||||
|
|
||||||
DOWNLOADED = None
|
DOWNLOADED = None
|
||||||
SNATCHED = None
|
SNATCHED = None
|
||||||
SNATCHED_PROPER = None
|
SNATCHED_PROPER = None
|
||||||
|
|
||||||
Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings.keys()]
|
Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings.keys()]
|
||||||
Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()]
|
Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()]
|
||||||
Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()]
|
Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()]
|
||||||
|
|
||||||
MP3 = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR], [])
|
MP3 = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR], [])
|
||||||
LOSSLESS = Quality.combineQualities([Quality.FLAC], [])
|
LOSSLESS = Quality.combineQualities([Quality.FLAC], [])
|
||||||
ANY = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR, Quality.FLAC], [])
|
ANY = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR, Quality.FLAC], [])
|
||||||
|
|
||||||
qualityPresets = (MP3, LOSSLESS, ANY)
|
qualityPresets = (MP3, LOSSLESS, ANY)
|
||||||
qualityPresetStrings = {MP3: "MP3 (All bitrates 192+)",
|
qualityPresetStrings = {MP3: "MP3 (All bitrates 192+)",
|
||||||
LOSSLESS: "Lossless (flac)",
|
LOSSLESS: "Lossless (flac)",
|
||||||
ANY: "Any"}
|
ANY: "Any"}
|
||||||
|
|||||||
+41
-41
@@ -1,41 +1,41 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
def ex(e):
|
def ex(e):
|
||||||
"""
|
"""
|
||||||
Returns a string from the exception text if it exists.
|
Returns a string from the exception text if it exists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
if not e.args or not e.args[0]:
|
if not e.args or not e.args[0]:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
e_message = e.args[0]
|
e_message = e.args[0]
|
||||||
|
|
||||||
# if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway
|
# if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway
|
||||||
if not e_message:
|
if not e_message:
|
||||||
try:
|
try:
|
||||||
e_message = str(e.args[0])
|
e_message = str(e.args[0])
|
||||||
except:
|
except:
|
||||||
e_message = ""
|
e_message = ""
|
||||||
|
|
||||||
return e_message
|
return e_message
|
||||||
|
|
||||||
|
|
||||||
class HeadphonesException(Exception):
|
class HeadphonesException(Exception):
|
||||||
"Generic Headphones Exception - should never be thrown, only subclassed"
|
"Generic Headphones Exception - should never be thrown, only subclassed"
|
||||||
|
|
||||||
class NewzbinAPIThrottled(HeadphonesException):
|
class NewzbinAPIThrottled(HeadphonesException):
|
||||||
"Newzbin has throttled us, deal with it"
|
"Newzbin has throttled us, deal with it"
|
||||||
|
|||||||
+162
-162
@@ -1,162 +1,162 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
## Stolen from Sick-Beard's sab.py ##
|
## Stolen from Sick-Beard's sab.py ##
|
||||||
#####################################
|
#####################################
|
||||||
|
|
||||||
import urllib, httplib
|
import urllib, httplib
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
from lib import MultipartPostHandler
|
from lib import MultipartPostHandler
|
||||||
import urllib2, cookielib
|
import urllib2, cookielib
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
from headphones.common import USER_AGENT
|
from headphones.common import USER_AGENT
|
||||||
from headphones import logger
|
from headphones import logger
|
||||||
from headphones import notifiers, helpers
|
from headphones import notifiers, helpers
|
||||||
|
|
||||||
def sendNZB(nzb):
|
def sendNZB(nzb):
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
if headphones.SAB_USERNAME:
|
if headphones.SAB_USERNAME:
|
||||||
params['ma_username'] = headphones.SAB_USERNAME
|
params['ma_username'] = headphones.SAB_USERNAME
|
||||||
if headphones.SAB_PASSWORD:
|
if headphones.SAB_PASSWORD:
|
||||||
params['ma_password'] = headphones.SAB_PASSWORD
|
params['ma_password'] = headphones.SAB_PASSWORD
|
||||||
if headphones.SAB_APIKEY:
|
if headphones.SAB_APIKEY:
|
||||||
params['apikey'] = headphones.SAB_APIKEY
|
params['apikey'] = headphones.SAB_APIKEY
|
||||||
if headphones.SAB_CATEGORY:
|
if headphones.SAB_CATEGORY:
|
||||||
params['cat'] = headphones.SAB_CATEGORY
|
params['cat'] = headphones.SAB_CATEGORY
|
||||||
|
|
||||||
# if it's a normal result we just pass SAB the URL
|
# if it's a normal result we just pass SAB the URL
|
||||||
if nzb.resultType == "nzb":
|
if nzb.resultType == "nzb":
|
||||||
# for newzbin results send the ID to sab specifically
|
# for newzbin results send the ID to sab specifically
|
||||||
if nzb.provider.getID() == 'newzbin':
|
if nzb.provider.getID() == 'newzbin':
|
||||||
id = nzb.provider.getIDFromURL(nzb.url)
|
id = nzb.provider.getIDFromURL(nzb.url)
|
||||||
if not id:
|
if not id:
|
||||||
logger.info("Unable to send NZB to sab, can't find ID in URL "+str(nzb.url))
|
logger.info("Unable to send NZB to sab, can't find ID in URL "+str(nzb.url))
|
||||||
return False
|
return False
|
||||||
params['mode'] = 'addid'
|
params['mode'] = 'addid'
|
||||||
params['name'] = id
|
params['name'] = id
|
||||||
else:
|
else:
|
||||||
params['mode'] = 'addurl'
|
params['mode'] = 'addurl'
|
||||||
params['name'] = nzb.url
|
params['name'] = nzb.url
|
||||||
|
|
||||||
# if we get a raw data result we want to upload it to SAB
|
# if we get a raw data result we want to upload it to SAB
|
||||||
elif nzb.resultType == "nzbdata":
|
elif nzb.resultType == "nzbdata":
|
||||||
# Sanitize the file a bit, since we can only use ascii chars with MultiPartPostHandler
|
# Sanitize the file a bit, since we can only use ascii chars with MultiPartPostHandler
|
||||||
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
||||||
params['mode'] = 'addfile'
|
params['mode'] = 'addfile'
|
||||||
multiPartParams = {"nzbfile": (nzb.name+".nzb", nzbdata)}
|
multiPartParams = {"nzbfile": (nzb.name+".nzb", nzbdata)}
|
||||||
|
|
||||||
if not headphones.SAB_HOST.startswith('http'):
|
if not headphones.SAB_HOST.startswith('http'):
|
||||||
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
||||||
|
|
||||||
if headphones.SAB_HOST.endswith('/'):
|
if headphones.SAB_HOST.endswith('/'):
|
||||||
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
||||||
|
|
||||||
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if nzb.resultType == "nzb":
|
if nzb.resultType == "nzb":
|
||||||
f = urllib.urlopen(url)
|
f = urllib.urlopen(url)
|
||||||
elif nzb.resultType == "nzbdata":
|
elif nzb.resultType == "nzbdata":
|
||||||
cookies = cookielib.CookieJar()
|
cookies = cookielib.CookieJar()
|
||||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
|
||||||
MultipartPostHandler.MultipartPostHandler)
|
MultipartPostHandler.MultipartPostHandler)
|
||||||
|
|
||||||
req = urllib2.Request(url,
|
req = urllib2.Request(url,
|
||||||
multiPartParams,
|
multiPartParams,
|
||||||
headers={'User-Agent': USER_AGENT})
|
headers={'User-Agent': USER_AGENT})
|
||||||
|
|
||||||
f = opener.open(req)
|
f = opener.open(req)
|
||||||
|
|
||||||
except (EOFError, IOError), e:
|
except (EOFError, IOError), e:
|
||||||
logger.error(u"Unable to connect to SAB with URL: %s" % url)
|
logger.error(u"Unable to connect to SAB with URL: %s" % url)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except httplib.InvalidURL, e:
|
except httplib.InvalidURL, e:
|
||||||
logger.error(u"Invalid SAB host, check your config. Current host: %s" % headphones.SAB_HOST)
|
logger.error(u"Invalid SAB host, check your config. Current host: %s" % headphones.SAB_HOST)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error(u"Error: " + str(e))
|
logger.error(u"Error: " + str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if f == None:
|
if f == None:
|
||||||
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = f.readlines()
|
result = f.readlines()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.info(u"Error trying to get result from SAB, NZB not sent: ")
|
logger.info(u"Error trying to get result from SAB, NZB not sent: ")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sabText = result[0].strip()
|
sabText = result[0].strip()
|
||||||
|
|
||||||
logger.info(u"Result text from SAB: " + sabText)
|
logger.info(u"Result text from SAB: " + sabText)
|
||||||
|
|
||||||
if sabText == "ok":
|
if sabText == "ok":
|
||||||
logger.info(u"NZB sent to SAB successfully")
|
logger.info(u"NZB sent to SAB successfully")
|
||||||
return True
|
return True
|
||||||
elif sabText == "Missing authentication":
|
elif sabText == "Missing authentication":
|
||||||
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.info(u"Unknown failure sending NZB to sab. Return text is: " + sabText)
|
logger.info(u"Unknown failure sending NZB to sab. Return text is: " + sabText)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def checkConfig():
|
def checkConfig():
|
||||||
|
|
||||||
params = { 'mode' : 'get_config',
|
params = { 'mode' : 'get_config',
|
||||||
'section' : 'misc'
|
'section' : 'misc'
|
||||||
}
|
}
|
||||||
|
|
||||||
if headphones.SAB_USERNAME:
|
if headphones.SAB_USERNAME:
|
||||||
params['ma_username'] = headphones.SAB_USERNAME
|
params['ma_username'] = headphones.SAB_USERNAME
|
||||||
if headphones.SAB_PASSWORD:
|
if headphones.SAB_PASSWORD:
|
||||||
params['ma_password'] = headphones.SAB_PASSWORD
|
params['ma_password'] = headphones.SAB_PASSWORD
|
||||||
if headphones.SAB_APIKEY:
|
if headphones.SAB_APIKEY:
|
||||||
params['apikey'] = headphones.SAB_APIKEY
|
params['apikey'] = headphones.SAB_APIKEY
|
||||||
|
|
||||||
if not headphones.SAB_HOST.startswith('http'):
|
if not headphones.SAB_HOST.startswith('http'):
|
||||||
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
||||||
|
|
||||||
if headphones.SAB_HOST.endswith('/'):
|
if headphones.SAB_HOST.endswith('/'):
|
||||||
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
||||||
|
|
||||||
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = urllib.urlopen(url).read()
|
f = urllib.urlopen(url).read()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.warn("Unable to read SABnzbd config file - cannot determine renaming options (might affect auto & forced post processing)")
|
logger.warn("Unable to read SABnzbd config file - cannot determine renaming options (might affect auto & forced post processing)")
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
|
|
||||||
config_options = ast.literal_eval(f)
|
config_options = ast.literal_eval(f)
|
||||||
|
|
||||||
replace_spaces = config_options['misc']['replace_spaces']
|
replace_spaces = config_options['misc']['replace_spaces']
|
||||||
replace_dots = config_options['misc']['replace_dots']
|
replace_dots = config_options['misc']['replace_dots']
|
||||||
|
|
||||||
return (replace_spaces, replace_dots)
|
return (replace_spaces, replace_dots)
|
||||||
|
|||||||
+87
-87
@@ -1,88 +1,88 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
####
|
####
|
||||||
# 06/2010 Nic Wolfe <nic@wolfeden.ca>
|
# 06/2010 Nic Wolfe <nic@wolfeden.ca>
|
||||||
# 02/2006 Will Holcomb <wholcomb@gmail.com>
|
# 02/2006 Will Holcomb <wholcomb@gmail.com>
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# License as published by the Free Software Foundation; either
|
||||||
# version 2.1 of the License, or (at your option) any later version.
|
# version 2.1 of the License, or (at your option) any later version.
|
||||||
#
|
#
|
||||||
# This library is distributed in the hope that it will be useful,
|
# This library is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
#
|
#
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
import urllib2
|
import urllib2
|
||||||
import mimetools, mimetypes
|
import mimetools, mimetypes
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
# Controls how sequences are uncoded. If true, elements may be given multiple values by
|
# Controls how sequences are uncoded. If true, elements may be given multiple values by
|
||||||
# assigning a sequence.
|
# assigning a sequence.
|
||||||
doseq = 1
|
doseq = 1
|
||||||
|
|
||||||
class MultipartPostHandler(urllib2.BaseHandler):
|
class MultipartPostHandler(urllib2.BaseHandler):
|
||||||
handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
|
handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
|
||||||
|
|
||||||
def http_request(self, request):
|
def http_request(self, request):
|
||||||
data = request.get_data()
|
data = request.get_data()
|
||||||
if data is not None and type(data) != str:
|
if data is not None and type(data) != str:
|
||||||
v_files = []
|
v_files = []
|
||||||
v_vars = []
|
v_vars = []
|
||||||
try:
|
try:
|
||||||
for(key, value) in data.items():
|
for(key, value) in data.items():
|
||||||
if type(value) in (file, list, tuple):
|
if type(value) in (file, list, tuple):
|
||||||
v_files.append((key, value))
|
v_files.append((key, value))
|
||||||
else:
|
else:
|
||||||
v_vars.append((key, value))
|
v_vars.append((key, value))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
systype, value, traceback = sys.exc_info()
|
systype, value, traceback = sys.exc_info()
|
||||||
raise TypeError, "not a valid non-string sequence or mapping object", traceback
|
raise TypeError, "not a valid non-string sequence or mapping object", traceback
|
||||||
|
|
||||||
if len(v_files) == 0:
|
if len(v_files) == 0:
|
||||||
data = urllib.urlencode(v_vars, doseq)
|
data = urllib.urlencode(v_vars, doseq)
|
||||||
else:
|
else:
|
||||||
boundary, data = MultipartPostHandler.multipart_encode(v_vars, v_files)
|
boundary, data = MultipartPostHandler.multipart_encode(v_vars, v_files)
|
||||||
contenttype = 'multipart/form-data; boundary=%s' % boundary
|
contenttype = 'multipart/form-data; boundary=%s' % boundary
|
||||||
if(request.has_header('Content-Type')
|
if(request.has_header('Content-Type')
|
||||||
and request.get_header('Content-Type').find('multipart/form-data') != 0):
|
and request.get_header('Content-Type').find('multipart/form-data') != 0):
|
||||||
print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data')
|
print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data')
|
||||||
request.add_unredirected_header('Content-Type', contenttype)
|
request.add_unredirected_header('Content-Type', contenttype)
|
||||||
|
|
||||||
request.add_data(data)
|
request.add_data(data)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def multipart_encode(vars, files, boundary = None, buffer = None):
|
def multipart_encode(vars, files, boundary = None, buffer = None):
|
||||||
if boundary is None:
|
if boundary is None:
|
||||||
boundary = mimetools.choose_boundary()
|
boundary = mimetools.choose_boundary()
|
||||||
if buffer is None:
|
if buffer is None:
|
||||||
buffer = ''
|
buffer = ''
|
||||||
for(key, value) in vars:
|
for(key, value) in vars:
|
||||||
buffer += '--%s\r\n' % boundary
|
buffer += '--%s\r\n' % boundary
|
||||||
buffer += 'Content-Disposition: form-data; name="%s"' % key
|
buffer += 'Content-Disposition: form-data; name="%s"' % key
|
||||||
buffer += '\r\n\r\n' + value + '\r\n'
|
buffer += '\r\n\r\n' + value + '\r\n'
|
||||||
for(key, fd) in files:
|
for(key, fd) in files:
|
||||||
|
|
||||||
# allow them to pass in a file or a tuple with name & data
|
# allow them to pass in a file or a tuple with name & data
|
||||||
if type(fd) == file:
|
if type(fd) == file:
|
||||||
name_in = fd.name
|
name_in = fd.name
|
||||||
fd.seek(0)
|
fd.seek(0)
|
||||||
data_in = fd.read()
|
data_in = fd.read()
|
||||||
elif type(fd) in (tuple, list):
|
elif type(fd) in (tuple, list):
|
||||||
name_in, data_in = fd
|
name_in, data_in = fd
|
||||||
|
|
||||||
filename = os.path.basename(name_in)
|
filename = os.path.basename(name_in)
|
||||||
contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||||
buffer += '--%s\r\n' % boundary
|
buffer += '--%s\r\n' % boundary
|
||||||
buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)
|
buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)
|
||||||
buffer += 'Content-Type: %s\r\n' % contenttype
|
buffer += 'Content-Type: %s\r\n' % contenttype
|
||||||
# buffer += 'Content-Length: %s\r\n' % file_size
|
# buffer += 'Content-Length: %s\r\n' % file_size
|
||||||
buffer += '\r\n' + data_in + '\r\n'
|
buffer += '\r\n' + data_in + '\r\n'
|
||||||
buffer += '--%s--\r\n\r\n' % boundary
|
buffer += '--%s--\r\n\r\n' % boundary
|
||||||
return boundary, buffer
|
return boundary, buffer
|
||||||
|
|
||||||
https_request = http_request
|
https_request = http_request
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
|
|
||||||
|
|||||||
+25
-25
@@ -1,25 +1,25 @@
|
|||||||
Copyright (c) 2004-2011, CherryPy Team (team@cherrypy.org)
|
Copyright (c) 2004-2011, CherryPy Team (team@cherrypy.org)
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
are permitted provided that the following conditions are met:
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
this list of conditions and the following disclaimer.
|
this list of conditions and the following disclaimer.
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
this list of conditions and the following disclaimer in the documentation
|
this list of conditions and the following disclaimer in the documentation
|
||||||
and/or other materials provided with the distribution.
|
and/or other materials provided with the distribution.
|
||||||
* Neither the name of the CherryPy Team nor the names of its contributors
|
* Neither the name of the CherryPy Team nor the names of its contributors
|
||||||
may be used to endorse or promote products derived from this software
|
may be used to endorse or promote products derived from this software
|
||||||
without specific prior written permission.
|
without specific prior written permission.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|||||||
+127
-127
@@ -1,127 +1,127 @@
|
|||||||
# Copyright (c) 2009 Raymond Hettinger
|
# Copyright (c) 2009 Raymond Hettinger
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person
|
# Permission is hereby granted, free of charge, to any person
|
||||||
# obtaining a copy of this software and associated documentation files
|
# obtaining a copy of this software and associated documentation files
|
||||||
# (the "Software"), to deal in the Software without restriction,
|
# (the "Software"), to deal in the Software without restriction,
|
||||||
# including without limitation the rights to use, copy, modify, merge,
|
# including without limitation the rights to use, copy, modify, merge,
|
||||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
# and to permit persons to whom the Software is furnished to do so,
|
# and to permit persons to whom the Software is furnished to do so,
|
||||||
# subject to the following conditions:
|
# subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be
|
# The above copyright notice and this permission notice shall be
|
||||||
# included in all copies or substantial portions of the Software.
|
# included in all copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
# OTHER DEALINGS IN THE SOFTWARE.
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
from UserDict import DictMixin
|
from UserDict import DictMixin
|
||||||
|
|
||||||
class OrderedDict(dict, DictMixin):
|
class OrderedDict(dict, DictMixin):
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||||
try:
|
try:
|
||||||
self.__end
|
self.__end
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.clear()
|
self.clear()
|
||||||
self.update(*args, **kwds)
|
self.update(*args, **kwds)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.__end = end = []
|
self.__end = end = []
|
||||||
end += [None, end, end] # sentinel node for doubly linked list
|
end += [None, end, end] # sentinel node for doubly linked list
|
||||||
self.__map = {} # key --> [key, prev, next]
|
self.__map = {} # key --> [key, prev, next]
|
||||||
dict.clear(self)
|
dict.clear(self)
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
if key not in self:
|
if key not in self:
|
||||||
end = self.__end
|
end = self.__end
|
||||||
curr = end[1]
|
curr = end[1]
|
||||||
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||||
dict.__setitem__(self, key, value)
|
dict.__setitem__(self, key, value)
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
dict.__delitem__(self, key)
|
dict.__delitem__(self, key)
|
||||||
key, prev, next = self.__map.pop(key)
|
key, prev, next = self.__map.pop(key)
|
||||||
prev[2] = next
|
prev[2] = next
|
||||||
next[1] = prev
|
next[1] = prev
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
end = self.__end
|
end = self.__end
|
||||||
curr = end[2]
|
curr = end[2]
|
||||||
while curr is not end:
|
while curr is not end:
|
||||||
yield curr[0]
|
yield curr[0]
|
||||||
curr = curr[2]
|
curr = curr[2]
|
||||||
|
|
||||||
def __reversed__(self):
|
def __reversed__(self):
|
||||||
end = self.__end
|
end = self.__end
|
||||||
curr = end[1]
|
curr = end[1]
|
||||||
while curr is not end:
|
while curr is not end:
|
||||||
yield curr[0]
|
yield curr[0]
|
||||||
curr = curr[1]
|
curr = curr[1]
|
||||||
|
|
||||||
def popitem(self, last=True):
|
def popitem(self, last=True):
|
||||||
if not self:
|
if not self:
|
||||||
raise KeyError('dictionary is empty')
|
raise KeyError('dictionary is empty')
|
||||||
if last:
|
if last:
|
||||||
key = reversed(self).next()
|
key = reversed(self).next()
|
||||||
else:
|
else:
|
||||||
key = iter(self).next()
|
key = iter(self).next()
|
||||||
value = self.pop(key)
|
value = self.pop(key)
|
||||||
return key, value
|
return key, value
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
items = [[k, self[k]] for k in self]
|
items = [[k, self[k]] for k in self]
|
||||||
tmp = self.__map, self.__end
|
tmp = self.__map, self.__end
|
||||||
del self.__map, self.__end
|
del self.__map, self.__end
|
||||||
inst_dict = vars(self).copy()
|
inst_dict = vars(self).copy()
|
||||||
self.__map, self.__end = tmp
|
self.__map, self.__end = tmp
|
||||||
if inst_dict:
|
if inst_dict:
|
||||||
return (self.__class__, (items,), inst_dict)
|
return (self.__class__, (items,), inst_dict)
|
||||||
return self.__class__, (items,)
|
return self.__class__, (items,)
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
return list(self)
|
return list(self)
|
||||||
|
|
||||||
setdefault = DictMixin.setdefault
|
setdefault = DictMixin.setdefault
|
||||||
update = DictMixin.update
|
update = DictMixin.update
|
||||||
pop = DictMixin.pop
|
pop = DictMixin.pop
|
||||||
values = DictMixin.values
|
values = DictMixin.values
|
||||||
items = DictMixin.items
|
items = DictMixin.items
|
||||||
iterkeys = DictMixin.iterkeys
|
iterkeys = DictMixin.iterkeys
|
||||||
itervalues = DictMixin.itervalues
|
itervalues = DictMixin.itervalues
|
||||||
iteritems = DictMixin.iteritems
|
iteritems = DictMixin.iteritems
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if not self:
|
if not self:
|
||||||
return '%s()' % (self.__class__.__name__,)
|
return '%s()' % (self.__class__.__name__,)
|
||||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return self.__class__(self)
|
return self.__class__(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromkeys(cls, iterable, value=None):
|
def fromkeys(cls, iterable, value=None):
|
||||||
d = cls()
|
d = cls()
|
||||||
for key in iterable:
|
for key in iterable:
|
||||||
d[key] = value
|
d[key] = value
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, OrderedDict):
|
if isinstance(other, OrderedDict):
|
||||||
if len(self) != len(other):
|
if len(self) != len(other):
|
||||||
return False
|
return False
|
||||||
for p, q in zip(self.items(), other.items()):
|
for p, q in zip(self.items(), other.items()):
|
||||||
if p != q:
|
if p != q:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
return dict.__eq__(self, other)
|
return dict.__eq__(self, other)
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|||||||
Reference in New Issue
Block a user