Converting crlf to just lf

Making end-of-lines consistent throughout the codebase.
This commit is contained in:
David
2014-08-06 17:00:29 +02:00
parent ce3b8b7894
commit fd30b3501e
11 changed files with 1993 additions and 1993 deletions
+63 -63
View File
@@ -1,63 +1,63 @@
#![preview thumb](https://github.com/rembo10/headphones/raw/master/data/images/headphoneslogo.png)Headphones #![preview thumb](https://github.com/rembo10/headphones/raw/master/data/images/headphoneslogo.png)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)
![preview thumb](http://i.imgur.com/LZO9a.png) ![preview thumb](http://i.imgur.com/LZO9a.png)
One of the many settings pages.... One of the many settings pages....
![preview thumb](http://i.imgur.com/xcWNy.png) ![preview thumb](http://i.imgur.com/xcWNy.png)
It might even know you better than you know yourself: It might even know you better than you know yourself:
![preview thumb](http://i.imgur.com/R7J0f.png) ![preview thumb](http://i.imgur.com/R7J0f.png)
Import Your Favorite Artists: Import Your Favorite Artists:
![preview thumb](http://i.imgur.com/6tZoC.png) ![preview thumb](http://i.imgur.com/6tZoC.png)
Artist Search Results (also search by album!): Artist Search Results (also search by album!):
![preview thumb](http://i.imgur.com/rIV0P.png) ![preview thumb](http://i.imgur.com/rIV0P.png)
Artist Page with Bio & Album Overview: Artist Page with Bio & Album Overview:
![preview thumb](http://i.imgur.com/SSil1.png) ![preview thumb](http://i.imgur.com/SSil1.png)
Album Page with track overview: Album Page with track overview:
![preview thumb](http://i.imgur.com/kcjES.png) ![preview thumb](http://i.imgur.com/kcjES.png)
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.
+25 -25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +1 @@
+25 -25
View File
@@ -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
View File
@@ -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